You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by en...@apache.org on 2026/08/27 13:13:01 UTC
(superset) 10/46: feat(dashboard-v2): add placeholder Data panel with mock datasets
This is an automated email from the ASF dual-hosted git repository.
EnxDev pushed a commit to branch enxdev/poc/dashboard-v2-editing-ui-flow
in repository https://gitbox.apache.org/repos/asf/superset.git
commit cbf4734dd1da395674699bd7e7768d23b15bc5cf
Author: Enzo Martellucci <en...@gmail.com>
AuthorDate: Wed Aug 26 01:51:24 2026 +0200
feat(dashboard-v2): add placeholder Data panel with mock datasets
---
.../pages/DashboardBuilderV2/DataPanel.test.tsx | 78 +++++++
.../src/pages/DashboardBuilderV2/DataPanel.tsx | 235 +++++++++++++++++++++
2 files changed, 313 insertions(+)
diff --git a/superset-frontend/src/pages/DashboardBuilderV2/DataPanel.test.tsx b/superset-frontend/src/pages/DashboardBuilderV2/DataPanel.test.tsx
new file mode 100644
index 00000000000..02e692c4552
--- /dev/null
+++ b/superset-frontend/src/pages/DashboardBuilderV2/DataPanel.test.tsx
@@ -0,0 +1,78 @@
+/**
+ * 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 userEvent from '@testing-library/user-event';
+import { render, screen, within } from 'spec/helpers/testing-library';
+import DataPanel from './DataPanel';
+
+const mount = () => render(<DataPanel />);
+
+test('lists the placeholder datasets, collapsed by default', () => {
+ mount();
+
+ expect(screen.getByTestId('data-panel-dataset-sales')).toBeVisible();
+ expect(screen.getByTestId('data-panel-dataset-coffee_sales')).toBeVisible();
+ expect(
+ screen.queryByTestId('data-panel-columns-sales'),
+ ).not.toBeInTheDocument();
+});
+
+test('expanding a dataset shows its columns', async () => {
+ mount();
+
+ const row = screen.getByTestId('data-panel-dataset-sales');
+ await userEvent.click(within(row).getByRole('button'));
+
+ const columns = screen.getByTestId('data-panel-columns-sales');
+ expect(columns).toHaveTextContent('order_id');
+ expect(columns).toHaveTextContent('order_date');
+ expect(columns).toHaveTextContent('sales_amount');
+ expect(columns).toHaveTextContent('region');
+});
+
+test('a second click collapses it again', async () => {
+ mount();
+ const row = screen.getByTestId('data-panel-dataset-sales');
+ const button = within(row).getByRole('button');
+
+ await userEvent.click(button);
+ await userEvent.click(button);
+
+ expect(
+ screen.queryByTestId('data-panel-columns-sales'),
+ ).not.toBeInTheDocument();
+});
+
+test('searching narrows the list to matching dataset names', async () => {
+ mount();
+
+ await userEvent.type(screen.getByTestId('data-panel-search'), 'coffee');
+
+ expect(screen.getByTestId('data-panel-dataset-coffee_sales')).toBeVisible();
+ expect(
+ screen.queryByTestId('data-panel-dataset-sales'),
+ ).not.toBeInTheDocument();
+});
+
+test('a search with no matches says so', async () => {
+ mount();
+
+ await userEvent.type(screen.getByTestId('data-panel-search'), 'nope');
+
+ expect(screen.getByTestId('data-panel-empty')).toBeVisible();
+});
diff --git a/superset-frontend/src/pages/DashboardBuilderV2/DataPanel.tsx b/superset-frontend/src/pages/DashboardBuilderV2/DataPanel.tsx
new file mode 100644
index 00000000000..7a3b91e5b15
--- /dev/null
+++ b/superset-frontend/src/pages/DashboardBuilderV2/DataPanel.tsx
@@ -0,0 +1,235 @@
+/**
+ * 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 { useState } from 'react';
+import type { ReactElement } from 'react';
+import { t } from '@apache-superset/core/translation';
+import { css, styled } from '@apache-superset/core/theme';
+import { GenericDataType } from '@apache-superset/core/common';
+import { EmptyState, Input } from '@superset-ui/core/components';
+import { Icons } from '@superset-ui/core/components/Icons';
+import { ColumnTypeLabel } from '@superset-ui/chart-controls';
+
+interface MockColumn {
+ readonly name: string;
+ readonly type: GenericDataType;
+}
+
+interface MockDataset {
+ readonly id: string;
+ readonly name: string;
+ readonly columns: readonly MockColumn[];
+}
+
+/**
+ * Static placeholder rows. The Data tab does not call the dataset API —
+ * a later change wires this list, and each dataset's columns, to
+ * `/api/v1/dataset/`, the same endpoint `datasetMetadata.ts` already reads a
+ * single bound dataset's columns from.
+ */
+const MOCK_DATASETS: readonly MockDataset[] = [
+ {
+ id: 'sales',
+ name: 'sales',
+ columns: [
+ { name: 'order_id', type: GenericDataType.String },
+ { name: 'order_date', type: GenericDataType.Temporal },
+ { name: 'sales_amount', type: GenericDataType.Numeric },
+ { name: 'region', type: GenericDataType.String },
+ ],
+ },
+ {
+ id: 'coffee_sales',
+ name: 'coffee_sales',
+ columns: [
+ { name: 'product', type: GenericDataType.String },
+ { name: 'roast_date', type: GenericDataType.Temporal },
+ { name: 'unit_price', type: GenericDataType.Numeric },
+ { name: 'is_decaf', type: GenericDataType.Boolean },
+ ],
+ },
+];
+
+const matches = (dataset: MockDataset, query: string): boolean =>
+ query.trim() === '' ||
+ dataset.name.toLowerCase().includes(query.trim().toLowerCase());
+
+/**
+ * The panel's own scroll column, set down from the tab bar and in from the
+ * panel edge — the same step `Palette`'s `Column` and `Outline`'s `Panel`
+ * take from theirs, so the four tabs of one rail start on one line.
+ */
+const Column = styled.div`
+ ${({ theme }) => css`
+ display: flex;
+ flex-direction: column;
+ gap: ${theme.sizeUnit * 5}px;
+ min-height: 0;
+ padding: ${theme.sizeUnit * 3}px ${theme.sizeUnit}px 0;
+ `}
+`;
+
+const List = styled.div`
+ ${({ theme }) => css`
+ display: flex;
+ flex-direction: column;
+ gap: ${theme.sizeUnit}px;
+ overflow-y: auto;
+ min-height: 0;
+ `}
+`;
+
+const DatasetButton = styled.button`
+ ${({ theme }) => css`
+ display: flex;
+ align-items: center;
+ gap: ${theme.sizeUnit * 2}px;
+ width: 100%;
+ padding: ${theme.sizeUnit * 2}px;
+ border: 1px solid ${theme.colorBorder};
+ border-radius: ${theme.borderRadiusSM}px;
+ background-color: ${theme.colorFillQuaternary};
+ color: ${theme.colorText};
+ font-size: ${theme.fontSizeSM}px;
+ text-align: left;
+ cursor: pointer;
+ transition: background-color ${theme.motionDurationMid};
+
+ .data-panel-chevron {
+ display: flex;
+ flex: 0 0 auto;
+ color: ${theme.colorTextTertiary};
+ }
+
+ &:hover {
+ background-color: ${theme.colorFillTertiary};
+ }
+
+ &:focus-visible {
+ outline: 2px solid ${theme.colorPrimaryBorder};
+ outline-offset: -2px;
+ }
+ `}
+`;
+
+const ColumnList = styled.div`
+ ${({ theme }) => css`
+ display: flex;
+ flex-direction: column;
+ gap: ${theme.sizeUnit}px;
+ margin-top: ${theme.sizeUnit}px;
+ margin-left: ${theme.sizeUnit * 2}px;
+ padding-left: ${theme.sizeUnit * 3}px;
+ border-left: 1px solid ${theme.colorBorder};
+ `}
+`;
+
+const ColumnRow = styled.div`
+ ${({ theme }) => css`
+ display: flex;
+ align-items: center;
+ gap: ${theme.sizeUnit * 2}px;
+ padding: ${theme.sizeUnit}px ${theme.sizeUnit * 2}px;
+ font-size: ${theme.fontSizeSM}px;
+ color: ${theme.colorText};
+ `}
+`;
+
+/**
+ * Datasets and their columns, to browse rather than to place.
+ *
+ * Building Blocks places widgets onto the canvas; this tab answers "what
+ * data is there to use" without touching any widget's binding — expanding a
+ * row reads its columns and nothing else happens.
+ */
+export default function DataPanel(): ReactElement {
+ const [query, setQuery] = useState('');
+ const [expanded, setExpanded] = useState<ReadonlySet<string>>(new Set());
+
+ const found = MOCK_DATASETS.filter(dataset => matches(dataset, query));
+
+ const toggle = (id: string): void =>
+ setExpanded(previous => {
+ const next = new Set(previous);
+ if (!next.delete(id)) {
+ next.add(id);
+ }
+ return next;
+ });
+
+ return (
+ <Column data-test="data-panel">
+ <Input
+ allowClear
+ value={query}
+ aria-label={t('Search datasets')}
+ placeholder={t('Search datasets…')}
+ data-test="data-panel-search"
+ prefix={<Icons.SearchOutlined iconSize="s" />}
+ onChange={event => setQuery(event.target.value)}
+ />
+ {found.length === 0 ? (
+ <div data-test="data-panel-empty">
+ <EmptyState
+ size="small"
+ image="filter-results.svg"
+ title={t('No matching datasets')}
+ description={t('Nothing here is called "%s".', query)}
+ />
+ </div>
+ ) : (
+ <List>
+ {found.map(dataset => {
+ const isOpen = expanded.has(dataset.id);
+ return (
+ <div
+ key={dataset.id}
+ data-test={`data-panel-dataset-${dataset.id}`}
+ >
+ <DatasetButton
+ type="button"
+ aria-expanded={isOpen}
+ onClick={() => toggle(dataset.id)}
+ >
+ <span className="data-panel-chevron" aria-hidden>
+ {isOpen ? (
+ <Icons.UpOutlined iconSize="s" />
+ ) : (
+ <Icons.DownOutlined iconSize="s" />
+ )}
+ </span>
+ {dataset.name}
+ </DatasetButton>
+ {isOpen && (
+ <ColumnList data-test={`data-panel-columns-${dataset.id}`}>
+ {dataset.columns.map(column => (
+ <ColumnRow key={column.name}>
+ <ColumnTypeLabel type={column.type} />
+ {column.name}
+ </ColumnRow>
+ ))}
+ </ColumnList>
+ )}
+ </div>
+ );
+ })}
+ </List>
+ )}
+ </Column>
+ );
+}