korbit-ai[bot] commented on code in PR #34222:
URL: https://github.com/apache/superset/pull/34222#discussion_r2215830076


##########
superset/report_templates/models.py:
##########
@@ -0,0 +1,25 @@
+from __future__ import annotations
+
+from flask_appbuilder import Model
+from sqlalchemy import Column, Integer, String, Text, ForeignKey
+from sqlalchemy.orm import relationship
+
+from superset.models.helpers import AuditMixinNullable
+from superset.connectors.sqla.models import SqlaTable
+
+
+class ReportTemplate(Model, AuditMixinNullable):
+    """Template for generating reports based on datasets."""
+
+    __tablename__ = "report_templates"
+
+    id = Column(Integer, primary_key=True)
+    name = Column(String(250), nullable=False)
+    description = Column(Text)
+    dataset_id = Column(Integer, ForeignKey("tables.id"), nullable=False)
+    template_path = Column(String(1024), nullable=False)

Review Comment:
   ### Unexplained Magic Number in Column Definition <sub>![category 
Readability](https://img.shields.io/badge/Readability-0284c7)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   The hardcoded string length 1024 for template_path lacks context and 
explanation for why this specific length was chosen.
   
   
   ###### Why this matters
   Future developers may be unsure if this limit is arbitrary or based on 
specific requirements, making it harder to maintain or modify the code 
confidently.
   
   ###### Suggested change ∙ *Feature Preview*
   ```python
   # Define a constant at module level
   MAX_TEMPLATE_PATH_LENGTH = 1024  # Maximum length based on common filesystem 
path limitations
   
   # Use in column definition
   template_path = Column(String(MAX_TEMPLATE_PATH_LENGTH), nullable=False)
   ```
   
   
   ###### Provide feedback to improve future suggestions
   [![Nice 
Catch](https://img.shields.io/badge/👍%20Nice%20Catch-71BC78)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/3cfae0d0-6a53-4b8d-8853-3a8eb836be2f/upvote)
 
[![Incorrect](https://img.shields.io/badge/👎%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/3cfae0d0-6a53-4b8d-8853-3a8eb836be2f?what_not_true=true)
  [![Not in 
Scope](https://img.shields.io/badge/👎%20Out%20of%20PR%20scope-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/3cfae0d0-6a53-4b8d-8853-3a8eb836be2f?what_out_of_scope=true)
 [![Not in coding 
standard](https://img.shields.io/badge/👎%20Not%20in%20our%20standards-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/3cfae0d0-6a53-4b8d-8853-3a8eb836be2f?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/👎%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/3cfae0d0-6a53-4b8d-8853-3a8eb836be2f)
   </details>
   
   <sub>
   
   💬 Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:16867f8a-6931-4aa0-abcb-e6b0ea0930c2 -->
   
   
   [](16867f8a-6931-4aa0-abcb-e6b0ea0930c2)



##########
superset-frontend/src/features/reportTemplates/UploadReportTemplateModal.tsx:
##########
@@ -0,0 +1,107 @@
+import { useState } from 'react';
+import { t, SupersetClient } from '@superset-ui/core';
+import { Modal, Input, Button } from '@superset-ui/core/components';
+import DatasetSelect from 
'src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DatasetSelect';
+
+interface UploadReportTemplateModalProps {
+  show: boolean;
+  onHide: () => void;
+  onUpload: () => void;
+  addDangerToast: (msg: string) => void;
+  addSuccessToast: (msg: string) => void;
+}
+
+export default function UploadReportTemplateModal({
+  show,
+  onHide,
+  onUpload,
+  addDangerToast,
+  addSuccessToast,
+}: UploadReportTemplateModalProps) {
+  const [name, setName] = useState('');
+  const [dataset, setDataset] = useState<{ label: string; value: number } | 
null>(null);
+  const [description, setDescription] = useState('');
+  const [file, setFile] = useState<File | null>(null);
+
+  const reset = () => {
+    setName('');
+    setDataset(null);
+    setDescription('');
+    setFile(null);
+  };
+
+  const handleUpload = () => {
+    if (!file || !name || !dataset) {
+      addDangerToast(t('All fields are required'));
+      return;
+    }
+    const form = new FormData();
+    form.append('template', file);
+    form.append('name', name);
+    form.append('dataset_id', String(dataset.value));
+    if (description) form.append('description', description);
+    SupersetClient.post({
+      endpoint: '/api/v1/report_template/',
+      postPayload: form,
+      stringify: false,
+    })
+      .then(() => {
+        addSuccessToast(t('Template uploaded'));
+        reset();
+        onUpload();
+        onHide();
+      })
+      .catch(err => {
+        addDangerToast(t('Failed to upload template: %s', err.message));
+      });

Review Comment:
   ### Insufficient Error Context in Catch Block <sub>![category Error 
Handling](https://img.shields.io/badge/Error%20Handling-ea580c)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   The error handling in the catch block only displays the error message 
without capturing additional context about the failed request.
   
   
   ###### Why this matters
   When debugging production issues, having only the error message may be 
insufficient to understand the root cause, especially for network or 
server-side failures.
   
   ###### Suggested change ∙ *Feature Preview*
   Enhance the error handler to include more context about the failed request 
and potentially log the error:
   ```typescript
   .catch(err => {
     console.error('Template upload failed:', { error: err, templateName: name, 
datasetId: dataset.value });
     const errorMessage = err.response?.data?.message || err.message;
     addDangerToast(t('Failed to upload template: %s', errorMessage));
   });
   ```
   
   
   ###### Provide feedback to improve future suggestions
   [![Nice 
Catch](https://img.shields.io/badge/👍%20Nice%20Catch-71BC78)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/38466169-d1e3-426f-860f-e741b524c3e7/upvote)
 
[![Incorrect](https://img.shields.io/badge/👎%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/38466169-d1e3-426f-860f-e741b524c3e7?what_not_true=true)
  [![Not in 
Scope](https://img.shields.io/badge/👎%20Out%20of%20PR%20scope-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/38466169-d1e3-426f-860f-e741b524c3e7?what_out_of_scope=true)
 [![Not in coding 
standard](https://img.shields.io/badge/👎%20Not%20in%20our%20standards-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/38466169-d1e3-426f-860f-e741b524c3e7?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/👎%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/38466169-d1e3-426f-860f-e741b524c3e7)
   </details>
   
   <sub>
   
   💬 Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:b00226e7-cc4e-490c-9119-a81001fe5763 -->
   
   
   [](b00226e7-cc4e-490c-9119-a81001fe5763)



##########
superset-frontend/src/features/reportTemplates/UploadReportTemplateModal.tsx:
##########
@@ -0,0 +1,107 @@
+import { useState } from 'react';
+import { t, SupersetClient } from '@superset-ui/core';
+import { Modal, Input, Button } from '@superset-ui/core/components';
+import DatasetSelect from 
'src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DatasetSelect';
+
+interface UploadReportTemplateModalProps {
+  show: boolean;
+  onHide: () => void;
+  onUpload: () => void;
+  addDangerToast: (msg: string) => void;
+  addSuccessToast: (msg: string) => void;
+}
+
+export default function UploadReportTemplateModal({
+  show,
+  onHide,
+  onUpload,
+  addDangerToast,
+  addSuccessToast,
+}: UploadReportTemplateModalProps) {
+  const [name, setName] = useState('');
+  const [dataset, setDataset] = useState<{ label: string; value: number } | 
null>(null);
+  const [description, setDescription] = useState('');
+  const [file, setFile] = useState<File | null>(null);
+
+  const reset = () => {
+    setName('');
+    setDataset(null);
+    setDescription('');
+    setFile(null);
+  };
+
+  const handleUpload = () => {
+    if (!file || !name || !dataset) {
+      addDangerToast(t('All fields are required'));
+      return;
+    }
+    const form = new FormData();
+    form.append('template', file);
+    form.append('name', name);
+    form.append('dataset_id', String(dataset.value));
+    if (description) form.append('description', description);
+    SupersetClient.post({
+      endpoint: '/api/v1/report_template/',
+      postPayload: form,
+      stringify: false,
+    })
+      .then(() => {
+        addSuccessToast(t('Template uploaded'));
+        reset();
+        onUpload();
+        onHide();
+      })
+      .catch(err => {
+        addDangerToast(t('Failed to upload template: %s', err.message));
+      });
+  };
+
+  return (
+    <Modal
+      show={show}
+      title={t('Add report template')}
+      onHide={onHide}
+      footer={
+        <div>
+          <Button buttonStyle="secondary" onClick={onHide}>
+            {t('Cancel')}
+          </Button>
+          <Button buttonStyle="primary" onClick={handleUpload}>
+            {t('Upload')}
+          </Button>
+        </div>
+      }
+    >
+      <div className="field">
+        <label htmlFor="template-name">{t('Name')}</label>
+        <Input
+          id="template-name"
+          value={name}
+          onChange={e => setName(e.target.value)}
+        />
+      </div>
+      <div className="field">
+        <label htmlFor="template-dataset">{t('Dataset')}</label>
+        <DatasetSelect
+          onChange={(opt: { label: string; value: number }) => setDataset(opt)}
+          value={dataset || undefined}
+        />
+      </div>
+      <div className="field">
+        <label htmlFor="template-desc">{t('Description')}</label>
+        <Input.TextArea
+          id="template-desc"
+          value={description}
+          onChange={e => setDescription(e.target.value)}
+        />
+      </div>
+      <div className="field">
+        <input
+          type="file"
+          accept=".odt"
+          onChange={e => setFile(e.target.files?.[0] || null)}
+        />

Review Comment:
   ### Inconsistent Form Element Styling <sub>![category 
Readability](https://img.shields.io/badge/Readability-0284c7)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   The file input lacks styling consistency with other form elements and 
missing label for accessibility.
   
   
   ###### Why this matters
   Inconsistent styling and missing labels reduce code maintainability and 
accessibility compliance.
   
   ###### Suggested change ∙ *Feature Preview*
   ```typescript
   <div className="field">
     <label htmlFor="template-file">{t('Template File (ODT)')}</label>
     <Input
       id="template-file"
       type="file"
       accept=".odt"
       onChange={e => setFile(e.target.files?.[0] || null)}
     />
   </div>
   ```
   
   
   ###### Provide feedback to improve future suggestions
   [![Nice 
Catch](https://img.shields.io/badge/👍%20Nice%20Catch-71BC78)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/11336825-dce4-45cc-9588-03e828c80350/upvote)
 
[![Incorrect](https://img.shields.io/badge/👎%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/11336825-dce4-45cc-9588-03e828c80350?what_not_true=true)
  [![Not in 
Scope](https://img.shields.io/badge/👎%20Out%20of%20PR%20scope-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/11336825-dce4-45cc-9588-03e828c80350?what_out_of_scope=true)
 [![Not in coding 
standard](https://img.shields.io/badge/👎%20Not%20in%20our%20standards-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/11336825-dce4-45cc-9588-03e828c80350?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/👎%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/11336825-dce4-45cc-9588-03e828c80350)
   </details>
   
   <sub>
   
   💬 Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:5701b6b3-a446-4508-a7bc-20c743d99b03 -->
   
   
   [](5701b6b3-a446-4508-a7bc-20c743d99b03)



##########
superset-frontend/src/features/reportTemplates/ReportTemplateModal.tsx:
##########
@@ -0,0 +1,91 @@
+import { useState, useEffect } from 'react';
+import { t, styled, SupersetClient } from '@superset-ui/core';
+import { Modal, Select, Button } from '@superset-ui/core/components';
+
+interface TemplateOption {
+  value: number;
+  label: string;
+}
+
+interface ReportTemplateModalProps {
+  show: boolean;
+  onHide: () => void;
+}
+
+const FullWidthSelect = styled(Select)`
+  width: 100%;
+`;
+
+export default function ReportTemplateModal({ show, onHide }: 
ReportTemplateModalProps) {
+  const [templates, setTemplates] = useState<TemplateOption[]>([]);
+  const [selected, setSelected] = useState<TemplateOption | null>(null);
+
+  useEffect(() => {
+    if (show) {
+      SupersetClient.get({ endpoint: '/api/v1/report_template/' })
+        .then(({ json }) => {
+          setTemplates(
+            (json.result || []).map((t: any) => ({ value: t.id, label: t.name 
})),
+          );
+        })
+        .catch(error => {
+          // eslint-disable-next-line no-console
+          console.error('Failed to load templates', error);

Review Comment:
   ### Direct Console Error Usage <sub>![category 
Logging](https://img.shields.io/badge/Logging-4f46e5)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   Using console.error for error logging instead of a proper logging framework.
   
   
   ###### Why this matters
   Console logging doesn't provide structured logging capabilities, timestamp 
information, log levels, or integration with logging aggregation systems. This 
makes it harder to monitor and debug issues in production.
   
   ###### Suggested change ∙ *Feature Preview*
   ```typescript
   import { logging } from '@superset-ui/core';
   // ...
   logging.error('Failed to load templates', { error, source: 
'ReportTemplateModal' });
   ```
   
   
   ###### Provide feedback to improve future suggestions
   [![Nice 
Catch](https://img.shields.io/badge/👍%20Nice%20Catch-71BC78)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/ee886fad-db59-4c69-8807-8bf6931c986e/upvote)
 
[![Incorrect](https://img.shields.io/badge/👎%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/ee886fad-db59-4c69-8807-8bf6931c986e?what_not_true=true)
  [![Not in 
Scope](https://img.shields.io/badge/👎%20Out%20of%20PR%20scope-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/ee886fad-db59-4c69-8807-8bf6931c986e?what_out_of_scope=true)
 [![Not in coding 
standard](https://img.shields.io/badge/👎%20Not%20in%20our%20standards-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/ee886fad-db59-4c69-8807-8bf6931c986e?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/👎%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/ee886fad-db59-4c69-8807-8bf6931c986e)
   </details>
   
   <sub>
   
   💬 Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:6aa320c6-549d-42b2-96f1-68fd5b28ed73 -->
   
   
   [](6aa320c6-549d-42b2-96f1-68fd5b28ed73)



##########
superset/migrations/versions/2025-06-07_00-00_b4757fa23b89_add_report_templates_table.py:
##########
@@ -0,0 +1,52 @@
+# 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.
+"""add report templates table
+
+Revision ID: b4757fa23b89
+Revises: 363a9b1e8992
+Create Date: 2025-06-07 00:00:00.000000
+
+"""
+
+import sqlalchemy as sa
+from alembic import op
+
+from superset.migrations.shared.utils import create_table
+
+# revision identifiers, used by Alembic.
+revision = "b4757fa23b89"
+down_revision = "363a9b1e8992"
+
+
+def upgrade() -> None:
+    create_table(
+        "report_templates",
+        sa.Column("id", sa.Integer(), nullable=False),

Review Comment:
   ### Non-optimized Primary Key Type <sub>![category 
Performance](https://img.shields.io/badge/Performance-4f46e5)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   Using Integer type for primary key without auto-increment may lead to 
performance issues as the table grows.
   
   
   ###### Why this matters
   Without auto-increment, the database needs to check for ID uniqueness on 
each insert, causing potential contention and slower inserts at scale.
   
   ###### Suggested change ∙ *Feature Preview*
   Use auto-incrementing Integer type for better insert performance:
   ```python
   sa.Column("id", sa.Integer(), autoincrement=True, nullable=False)
   ```
   
   
   ###### Provide feedback to improve future suggestions
   [![Nice 
Catch](https://img.shields.io/badge/👍%20Nice%20Catch-71BC78)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/b954e48b-f9b2-4084-b662-52490a2a00cf/upvote)
 
[![Incorrect](https://img.shields.io/badge/👎%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/b954e48b-f9b2-4084-b662-52490a2a00cf?what_not_true=true)
  [![Not in 
Scope](https://img.shields.io/badge/👎%20Out%20of%20PR%20scope-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/b954e48b-f9b2-4084-b662-52490a2a00cf?what_out_of_scope=true)
 [![Not in coding 
standard](https://img.shields.io/badge/👎%20Not%20in%20our%20standards-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/b954e48b-f9b2-4084-b662-52490a2a00cf?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/👎%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/b954e48b-f9b2-4084-b662-52490a2a00cf)
   </details>
   
   <sub>
   
   💬 Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:3b935247-5207-4fb5-ba15-b22fa0e29c9e -->
   
   
   [](3b935247-5207-4fb5-ba15-b22fa0e29c9e)



##########
superset/migrations/versions/2025-06-07_00-00_b4757fa23b89_add_report_templates_table.py:
##########
@@ -0,0 +1,52 @@
+# 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.
+"""add report templates table
+
+Revision ID: b4757fa23b89
+Revises: 363a9b1e8992
+Create Date: 2025-06-07 00:00:00.000000
+
+"""
+
+import sqlalchemy as sa
+from alembic import op
+
+from superset.migrations.shared.utils import create_table
+
+# revision identifiers, used by Alembic.
+revision = "b4757fa23b89"
+down_revision = "363a9b1e8992"
+
+
+def upgrade() -> None:
+    create_table(
+        "report_templates",
+        sa.Column("id", sa.Integer(), nullable=False),
+        sa.Column("created_on", sa.DateTime(), nullable=True),
+        sa.Column("changed_on", sa.DateTime(), nullable=True),

Review Comment:
   ### Nullable Audit Timestamps <sub>![category 
Functionality](https://img.shields.io/badge/Functionality-0284c7)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   Audit timestamps (created_on and changed_on) are marked as nullable, but 
should be non-nullable with default values.
   
   
   ###### Why this matters
   Allowing null values for audit timestamps can lead to gaps in tracking when 
records were created or modified, compromising data audit capabilities.
   
   ###### Suggested change ∙ *Feature Preview*
   Make timestamps non-nullable with defaults:
   ```python
   sa.Column("created_on", sa.DateTime(), nullable=False, 
server_default=sa.func.now()),
   sa.Column("changed_on", sa.DateTime(), nullable=False, 
server_default=sa.func.now()),
   ```
   
   
   ###### Provide feedback to improve future suggestions
   [![Nice 
Catch](https://img.shields.io/badge/👍%20Nice%20Catch-71BC78)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/602d4b6a-6bf0-458c-b0af-9aec1258914f/upvote)
 
[![Incorrect](https://img.shields.io/badge/👎%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/602d4b6a-6bf0-458c-b0af-9aec1258914f?what_not_true=true)
  [![Not in 
Scope](https://img.shields.io/badge/👎%20Out%20of%20PR%20scope-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/602d4b6a-6bf0-458c-b0af-9aec1258914f?what_out_of_scope=true)
 [![Not in coding 
standard](https://img.shields.io/badge/👎%20Not%20in%20our%20standards-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/602d4b6a-6bf0-458c-b0af-9aec1258914f?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/👎%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/602d4b6a-6bf0-458c-b0af-9aec1258914f)
   </details>
   
   <sub>
   
   💬 Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:eedfdc0c-4da3-43dd-b7fa-606e4a05c369 -->
   
   
   [](eedfdc0c-4da3-43dd-b7fa-606e4a05c369)



##########
superset-frontend/src/features/reportTemplates/UploadReportTemplateModal.tsx:
##########
@@ -0,0 +1,107 @@
+import { useState } from 'react';
+import { t, SupersetClient } from '@superset-ui/core';
+import { Modal, Input, Button } from '@superset-ui/core/components';
+import DatasetSelect from 
'src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DatasetSelect';
+
+interface UploadReportTemplateModalProps {
+  show: boolean;
+  onHide: () => void;
+  onUpload: () => void;
+  addDangerToast: (msg: string) => void;
+  addSuccessToast: (msg: string) => void;
+}
+
+export default function UploadReportTemplateModal({
+  show,
+  onHide,
+  onUpload,
+  addDangerToast,
+  addSuccessToast,
+}: UploadReportTemplateModalProps) {
+  const [name, setName] = useState('');
+  const [dataset, setDataset] = useState<{ label: string; value: number } | 
null>(null);
+  const [description, setDescription] = useState('');
+  const [file, setFile] = useState<File | null>(null);
+
+  const reset = () => {
+    setName('');
+    setDataset(null);
+    setDescription('');
+    setFile(null);
+  };
+
+  const handleUpload = () => {
+    if (!file || !name || !dataset) {
+      addDangerToast(t('All fields are required'));
+      return;
+    }
+    const form = new FormData();
+    form.append('template', file);
+    form.append('name', name);
+    form.append('dataset_id', String(dataset.value));
+    if (description) form.append('description', description);
+    SupersetClient.post({
+      endpoint: '/api/v1/report_template/',
+      postPayload: form,
+      stringify: false,
+    })
+      .then(() => {
+        addSuccessToast(t('Template uploaded'));
+        reset();
+        onUpload();
+        onHide();
+      })
+      .catch(err => {
+        addDangerToast(t('Failed to upload template: %s', err.message));
+      });
+  };
+
+  return (
+    <Modal
+      show={show}
+      title={t('Add report template')}
+      onHide={onHide}
+      footer={
+        <div>
+          <Button buttonStyle="secondary" onClick={onHide}>
+            {t('Cancel')}
+          </Button>
+          <Button buttonStyle="primary" onClick={handleUpload}>
+            {t('Upload')}
+          </Button>
+        </div>
+      }
+    >
+      <div className="field">
+        <label htmlFor="template-name">{t('Name')}</label>
+        <Input
+          id="template-name"
+          value={name}
+          onChange={e => setName(e.target.value)}

Review Comment:
   ### Missing Input Sanitization <sub>![category 
Security](https://img.shields.io/badge/Security-e11d48)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   User input for template name is not sanitized or validated for length or 
special characters.
   
   
   ###### Why this matters
   Unsanitized input could lead to XSS attacks if the template name is rendered 
elsewhere in the application or cause issues with file system operations.
   
   ###### Suggested change ∙ *Feature Preview*
   ```typescript
   const sanitizeName = (input: string) => {
     // Remove special characters and limit length
     return input.replace(/[^a-zA-Z0-9-_ ]/g, '').slice(0, 255);
   };
   
   onChange={e => setName(sanitizeName(e.target.value))}
   ```
   
   
   ###### Provide feedback to improve future suggestions
   [![Nice 
Catch](https://img.shields.io/badge/👍%20Nice%20Catch-71BC78)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/0a063e34-6bf3-4073-ad0a-2150974bf18a/upvote)
 
[![Incorrect](https://img.shields.io/badge/👎%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/0a063e34-6bf3-4073-ad0a-2150974bf18a?what_not_true=true)
  [![Not in 
Scope](https://img.shields.io/badge/👎%20Out%20of%20PR%20scope-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/0a063e34-6bf3-4073-ad0a-2150974bf18a?what_out_of_scope=true)
 [![Not in coding 
standard](https://img.shields.io/badge/👎%20Not%20in%20our%20standards-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/0a063e34-6bf3-4073-ad0a-2150974bf18a?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/👎%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/0a063e34-6bf3-4073-ad0a-2150974bf18a)
   </details>
   
   <sub>
   
   💬 Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:10453429-1753-419c-a2bb-913289735bf0 -->
   
   
   [](10453429-1753-419c-a2bb-913289735bf0)



##########
superset-frontend/src/features/reportTemplates/ReportTemplateModal.tsx:
##########
@@ -0,0 +1,91 @@
+import { useState, useEffect } from 'react';
+import { t, styled, SupersetClient } from '@superset-ui/core';
+import { Modal, Select, Button } from '@superset-ui/core/components';
+
+interface TemplateOption {
+  value: number;
+  label: string;
+}
+
+interface ReportTemplateModalProps {
+  show: boolean;
+  onHide: () => void;
+}
+
+const FullWidthSelect = styled(Select)`
+  width: 100%;
+`;
+
+export default function ReportTemplateModal({ show, onHide }: 
ReportTemplateModalProps) {
+  const [templates, setTemplates] = useState<TemplateOption[]>([]);
+  const [selected, setSelected] = useState<TemplateOption | null>(null);
+
+  useEffect(() => {
+    if (show) {
+      SupersetClient.get({ endpoint: '/api/v1/report_template/' })
+        .then(({ json }) => {
+          setTemplates(
+            (json.result || []).map((t: any) => ({ value: t.id, label: t.name 
})),
+          );
+        })
+        .catch(error => {
+          // eslint-disable-next-line no-console
+          console.error('Failed to load templates', error);
+        });
+    }
+  }, [show]);
+
+  const onGenerate = () => {
+    if (!selected) return;
+    SupersetClient.post({
+      endpoint: `/api/v1/report_template/${selected}/generate`,
+      parseMethod: 'raw',
+    })

Review Comment:
   ### Unvalidated Template ID in API Endpoint <sub>![category 
Security](https://img.shields.io/badge/Security-e11d48)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   The selected template ID is directly interpolated into the API endpoint URL 
without validation, which could potentially lead to path traversal attacks.
   
   
   ###### Why this matters
   If the selected value is manipulated, it could allow attackers to access 
unintended API endpoints or cause unexpected behavior in the API request.
   
   ###### Suggested change ∙ *Feature Preview*
   Validate that selected.value is a positive integer before using it in the 
URL:
   ```typescript
   SupersetClient.post({
     endpoint: `/api/v1/report_template/${selected.value > 0 ? selected.value : 
''}/generate`,
     parseMethod: 'raw',
   })
   ```
   
   
   ###### Provide feedback to improve future suggestions
   [![Nice 
Catch](https://img.shields.io/badge/👍%20Nice%20Catch-71BC78)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/c1db4d69-d4f3-40aa-b13f-ef90770efc0f/upvote)
 
[![Incorrect](https://img.shields.io/badge/👎%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/c1db4d69-d4f3-40aa-b13f-ef90770efc0f?what_not_true=true)
  [![Not in 
Scope](https://img.shields.io/badge/👎%20Out%20of%20PR%20scope-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/c1db4d69-d4f3-40aa-b13f-ef90770efc0f?what_out_of_scope=true)
 [![Not in coding 
standard](https://img.shields.io/badge/👎%20Not%20in%20our%20standards-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/c1db4d69-d4f3-40aa-b13f-ef90770efc0f?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/👎%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/c1db4d69-d4f3-40aa-b13f-ef90770efc0f)
   </details>
   
   <sub>
   
   💬 Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:7ca96b2a-9f09-4a8f-8f22-45654f93a2bf -->
   
   
   [](7ca96b2a-9f09-4a8f-8f22-45654f93a2bf)



##########
superset-frontend/src/features/reportTemplates/ReportTemplateModal.tsx:
##########
@@ -0,0 +1,91 @@
+import { useState, useEffect } from 'react';
+import { t, styled, SupersetClient } from '@superset-ui/core';
+import { Modal, Select, Button } from '@superset-ui/core/components';
+
+interface TemplateOption {
+  value: number;
+  label: string;
+}
+
+interface ReportTemplateModalProps {
+  show: boolean;
+  onHide: () => void;
+}
+
+const FullWidthSelect = styled(Select)`
+  width: 100%;
+`;
+
+export default function ReportTemplateModal({ show, onHide }: 
ReportTemplateModalProps) {
+  const [templates, setTemplates] = useState<TemplateOption[]>([]);
+  const [selected, setSelected] = useState<TemplateOption | null>(null);
+
+  useEffect(() => {
+    if (show) {
+      SupersetClient.get({ endpoint: '/api/v1/report_template/' })
+        .then(({ json }) => {
+          setTemplates(
+            (json.result || []).map((t: any) => ({ value: t.id, label: t.name 
})),
+          );
+        })
+        .catch(error => {
+          // eslint-disable-next-line no-console
+          console.error('Failed to load templates', error);
+        });
+    }
+  }, [show]);
+
+  const onGenerate = () => {
+    if (!selected) return;
+    SupersetClient.post({
+      endpoint: `/api/v1/report_template/${selected}/generate`,

Review Comment:
   ### Incorrect template ID in API endpoint <sub>![category 
Functionality](https://img.shields.io/badge/Functionality-0284c7)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   The API endpoint uses the entire selected object instead of selected.value 
for the template ID
   
   
   ###### Why this matters
   This will result in a malformed API URL causing the report generation to 
fail as the endpoint expects a numeric ID
   
   ###### Suggested change ∙ *Feature Preview*
   Use the template ID from the selected object:
   ```typescript
   endpoint: `/api/v1/report_template/${selected.value}/generate`,
   ```
   
   
   ###### Provide feedback to improve future suggestions
   [![Nice 
Catch](https://img.shields.io/badge/👍%20Nice%20Catch-71BC78)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/c585c208-271a-4757-9a5b-12e62f32fda4/upvote)
 
[![Incorrect](https://img.shields.io/badge/👎%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/c585c208-271a-4757-9a5b-12e62f32fda4?what_not_true=true)
  [![Not in 
Scope](https://img.shields.io/badge/👎%20Out%20of%20PR%20scope-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/c585c208-271a-4757-9a5b-12e62f32fda4?what_out_of_scope=true)
 [![Not in coding 
standard](https://img.shields.io/badge/👎%20Not%20in%20our%20standards-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/c585c208-271a-4757-9a5b-12e62f32fda4?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/👎%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/c585c208-271a-4757-9a5b-12e62f32fda4)
   </details>
   
   <sub>
   
   💬 Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:c9b4c45c-752b-4b94-af2b-f13e6fde692c -->
   
   
   [](c9b4c45c-752b-4b94-af2b-f13e6fde692c)



##########
superset/report_templates/models.py:
##########
@@ -0,0 +1,25 @@
+from __future__ import annotations
+
+from flask_appbuilder import Model
+from sqlalchemy import Column, Integer, String, Text, ForeignKey
+from sqlalchemy.orm import relationship
+
+from superset.models.helpers import AuditMixinNullable
+from superset.connectors.sqla.models import SqlaTable
+
+
+class ReportTemplate(Model, AuditMixinNullable):
+    """Template for generating reports based on datasets."""
+
+    __tablename__ = "report_templates"
+
+    id = Column(Integer, primary_key=True)
+    name = Column(String(250), nullable=False)
+    description = Column(Text)
+    dataset_id = Column(Integer, ForeignKey("tables.id"), nullable=False)
+    template_path = Column(String(1024), nullable=False)
+
+    dataset = relationship(SqlaTable, foreign_keys=[dataset_id])

Review Comment:
   ### Undefined Dataset Relationship Cascade <sub>![category 
Functionality](https://img.shields.io/badge/Functionality-0284c7)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   No cascading delete behavior specified for the dataset relationship
   
   
   ###### Why this matters
   If a dataset is deleted, related templates may become orphaned and cause 
issues during report generation
   
   ###### Suggested change ∙ *Feature Preview*
   Define appropriate cascade behavior for the dataset relationship:
   ```python
   dataset = relationship(
       SqlaTable,
       foreign_keys=[dataset_id],
       cascade='all, delete-orphan'
   )
   ```
   
   
   ###### Provide feedback to improve future suggestions
   [![Nice 
Catch](https://img.shields.io/badge/👍%20Nice%20Catch-71BC78)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/ed3ebf5c-bad5-4915-818a-c01f4c95757c/upvote)
 
[![Incorrect](https://img.shields.io/badge/👎%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/ed3ebf5c-bad5-4915-818a-c01f4c95757c?what_not_true=true)
  [![Not in 
Scope](https://img.shields.io/badge/👎%20Out%20of%20PR%20scope-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/ed3ebf5c-bad5-4915-818a-c01f4c95757c?what_out_of_scope=true)
 [![Not in coding 
standard](https://img.shields.io/badge/👎%20Not%20in%20our%20standards-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/ed3ebf5c-bad5-4915-818a-c01f4c95757c?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/👎%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/ed3ebf5c-bad5-4915-818a-c01f4c95757c)
   </details>
   
   <sub>
   
   💬 Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:9ef3e315-f2fd-4e7b-bf17-921866a553dd -->
   
   
   [](9ef3e315-f2fd-4e7b-bf17-921866a553dd)



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to