Compare commits

...

3 Commits

Author SHA1 Message Date
Maxime Beauchemin
ddd08b5fe7 style(dataset-modal): fix prettier formatting
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 01:14:09 +00:00
Maxime Beauchemin
63901941ff fix(dataset-modal): fix Source tab spacing per sc-101861
- Add bottom margin to radio buttons section so lock icon/text has
  more separation from the divider line (issue 1)
- Remove last FormItem bottom margin in both virtual and physical
  datasource views to reduce excess whitespace below the SQL text box
  and table selector (issue 2)
- Move search inputs into CollectionTable toolbar via new toolbarExtra
  prop so search and "Add item" button sit on the same row
- Update CrudButtonWrapper to use flex layout with space-between for
  proper toolbar alignment

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 19:47:21 +00:00
Maxime Beauchemin
42f1af87bf fix(dataset-modal): fix UI alignment issues in Edit Dataset modal
- Remove negative margin hack on .ant-tabs-top that caused tabs to overlap
  with the warning alert, replace with tighter alert bottom margin
- Remove width calc(100%-34px) and marginTop:-16 hacks on Source tab's
  table_name field that caused it to be misaligned with the database selector
- Align Columns tab search input and "Sync columns" button on the same row
  using Flex for consistent layout
- Replace hardcoded inline style={{ marginBottom: 16, width: 300 }} on
  search inputs with theme-token-based css props
- Remove leading whitespace in warning alert text
- Remove unused ColumnButtonWrapper and StyledButtonWrapper styled components
- Use css prop instead of inline style for Settings tab overflow wrapper

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 16:18:58 +00:00
4 changed files with 118 additions and 100 deletions

View File

@@ -65,10 +65,6 @@ const StyledDatasourceModal = styled(Modal)`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.ant-tabs-top {
margin-top: -${({ theme }) => theme.sizeUnit * 4}px;
}
`; `;
function buildExtraJsonObject( function buildExtraJsonObject(

View File

@@ -38,7 +38,9 @@ import {
} from '../../types'; } from '../../types';
const CrudButtonWrapper = styled.div` const CrudButtonWrapper = styled.div`
text-align: right; display: flex;
justify-content: space-between;
align-items: center;
${({ theme }) => `margin-bottom: ${theme.sizeUnit * 2}px`} ${({ theme }) => `margin-bottom: ${theme.sizeUnit * 2}px`}
`; `;
@@ -466,24 +468,27 @@ export default class CRUDCollection extends PureComponent<
return ( return (
<> <>
<CrudButtonWrapper> {(this.props.toolbarExtra || this.props.allowAddItem) && (
{this.props.allowAddItem && ( <CrudButtonWrapper>
<StyledButtonWrapper> {this.props.toolbarExtra}
<Button {this.props.allowAddItem && (
buttonSize="small" <StyledButtonWrapper>
buttonStyle="secondary" <Button
onClick={this.onAddItem} buttonSize="small"
data-test="add-item-button" buttonStyle="secondary"
> onClick={this.onAddItem}
<Icons.PlusOutlined data-test="add-item-button"
iconSize="m" >
data-test="crud-add-table-item" <Icons.PlusOutlined
/> iconSize="m"
{t('Add item')} data-test="crud-add-table-item"
</Button> />
</StyledButtonWrapper> {t('Add item')}
)} </Button>
</CrudButtonWrapper> </StyledButtonWrapper>
)}
</CrudButtonWrapper>
)}
<Table<CollectionItem> <Table<CollectionItem>
data-test="crud-table" data-test="crud-table"
columns={tableColumns} columns={tableColumns}

View File

@@ -311,6 +311,7 @@ interface ColumnCollectionTableProps {
columnLabelTooltips?: Record<string, string>; columnLabelTooltips?: Record<string, string>;
filterTerm?: string; filterTerm?: string;
filterFields?: string[]; filterFields?: string[];
toolbarExtra?: ReactNode;
} }
interface StackedFieldProps { interface StackedFieldProps {
@@ -399,11 +400,6 @@ const EditLockContainer = styled.div`
} }
`; `;
const ColumnButtonWrapper = styled.div`
text-align: right;
${({ theme }) => `margin-bottom: ${theme.sizeUnit * 2}px`}
`;
const StyledLabelWrapper = styled.div` const StyledLabelWrapper = styled.div`
display: flex; display: flex;
align-items: center; align-items: center;
@@ -447,16 +443,6 @@ const FieldLabelWithTooltip = styled.div`
`} `}
`; `;
const StyledButtonWrapper = styled.span`
${({ theme }) => `
margin-top: ${theme.sizeUnit * 3}px;
margin-left: ${theme.sizeUnit * 3}px;
button>span>:first-of-type {
margin-right: 0;
}
`}
`;
const checkboxGenerator = ( const checkboxGenerator = (
d: boolean, d: boolean,
onChange: (value: boolean) => void, onChange: (value: boolean) => void,
@@ -532,6 +518,7 @@ function ColumnCollectionTable({
columnLabelTooltips, columnLabelTooltips,
filterTerm, filterTerm,
filterFields, filterFields,
toolbarExtra,
}: ColumnCollectionTableProps): JSX.Element { }: ColumnCollectionTableProps): JSX.Element {
return ( return (
<CollectionTable <CollectionTable
@@ -566,6 +553,7 @@ function ColumnCollectionTable({
columnLabelTooltips={columnLabelTooltips} columnLabelTooltips={columnLabelTooltips}
filterTerm={filterTerm} filterTerm={filterTerm}
filterFields={filterFields} filterFields={filterFields}
toolbarExtra={toolbarExtra}
stickyHeader stickyHeader
expandFieldset={ expandFieldset={
<FormContainer> <FormContainer>
@@ -1835,6 +1823,7 @@ class DatasourceEditor extends PureComponent<
<div <div
css={theme => css` css={theme => css`
margin-top: ${theme.sizeUnit * 3}px; margin-top: ${theme.sizeUnit * 3}px;
margin-bottom: ${theme.sizeUnit * 2}px;
display: flex; display: flex;
gap: ${theme.sizeUnit * 4}px; gap: ${theme.sizeUnit * 4}px;
`} `}
@@ -1854,7 +1843,13 @@ class DatasourceEditor extends PureComponent<
<Divider /> <Divider />
<Fieldset item={datasource} onChange={this.onDatasourceChange} compact> <Fieldset item={datasource} onChange={this.onDatasourceChange} compact>
{this.state.datasourceType === DATASOURCE_TYPES.virtual.key && ( {this.state.datasourceType === DATASOURCE_TYPES.virtual.key && (
<div> <div
css={css`
.ant-form-item:last-child {
margin-bottom: 0;
}
`}
>
{this.state.isSqla && ( {this.state.isSqla && (
<> <>
<Col xs={24} md={12}> <Col xs={24} md={12}>
@@ -1897,22 +1892,20 @@ class DatasourceEditor extends PureComponent<
</div> </div>
} }
/> />
<div css={{ width: 'calc(100% - 34px)', marginTop: -16 }}> <Field
<Field fieldKey="table_name"
fieldKey="table_name" label={t('Name')}
label={t('Name')} control={
control={ <TextControl
<TextControl controlId="table_name"
controlId="table_name" onChange={table => {
onChange={table => { this.onDatasourcePropChange('table_name', table);
this.onDatasourcePropChange('table_name', table); }}
}} placeholder={t('Dataset name')}
placeholder={t('Dataset name')} disabled={!this.state.isEditMode}
disabled={!this.state.isEditMode} />
/> }
} />
/>
</div>
</Col> </Col>
<Field <Field
fieldKey="sql" fieldKey="sql"
@@ -2053,7 +2046,15 @@ class DatasourceEditor extends PureComponent<
</div> </div>
)} )}
{this.state.datasourceType === DATASOURCE_TYPES.physical.key && ( {this.state.datasourceType === DATASOURCE_TYPES.physical.key && (
<Col xs={24} md={12}> <Col
xs={24}
md={12}
css={css`
.ant-form-item:last-child {
margin-bottom: 0;
}
`}
>
{this.state.isSqla && ( {this.state.isSqla && (
<Field <Field
fieldKey="tableSelector" fieldKey="tableSelector"
@@ -2148,18 +2149,24 @@ class DatasourceEditor extends PureComponent<
const sortedMetrics = metrics?.length ? this.sortMetrics(metrics) : []; const sortedMetrics = metrics?.length ? this.sortMetrics(metrics) : [];
return ( return (
<div> <div>
<Input.Search
placeholder={t('Search metrics by key or label')}
value={metricSearchTerm}
onChange={e => this.setState({ metricSearchTerm: e.target.value })}
style={{ marginBottom: 16, width: 300 }}
allowClear
/>
<CollectionTable <CollectionTable
tableColumns={['metric_name', 'verbose_name', 'expression']} tableColumns={['metric_name', 'verbose_name', 'expression']}
sortColumns={['metric_name', 'verbose_name', 'expression']} sortColumns={['metric_name', 'verbose_name', 'expression']}
filterTerm={metricSearchTerm} filterTerm={metricSearchTerm}
filterFields={['metric_name', 'verbose_name']} filterFields={['metric_name', 'verbose_name']}
toolbarExtra={
<Input.Search
placeholder={t('Search metrics by key or label')}
value={metricSearchTerm}
onChange={e =>
this.setState({ metricSearchTerm: e.target.value })
}
css={theme => ({
width: theme.sizeUnit * 75,
})}
allowClear
/>
}
columnLabels={{ columnLabels={{
metric_name: t('Metric Key'), metric_name: t('Metric Key'),
verbose_name: t('Label'), verbose_name: t('Label'),
@@ -2336,11 +2343,10 @@ class DatasourceEditor extends PureComponent<
<DatasourceContainer data-test="datasource-editor"> <DatasourceContainer data-test="datasource-editor">
{this.renderErrors()} {this.renderErrors()}
<Alert <Alert
css={theme => ({ marginBottom: theme.sizeUnit * 4 })} css={theme => ({ marginBottom: theme.sizeUnit * 2 })}
type="warning" type="warning"
message={ message={
<> <>
{' '}
<strong>{t('Be careful.')} </strong> <strong>{t('Be careful.')} </strong>
{t( {t(
'Changing these settings will affect all charts using this dataset, including charts owned by other people.', 'Changing these settings will affect all charts using this dataset, including charts owned by other people.',
@@ -2380,29 +2386,35 @@ class DatasourceEditor extends PureComponent<
children: ( children: (
<StyledTableTabWrapper> <StyledTableTabWrapper>
{this.renderDefaultColumnSettings()} {this.renderDefaultColumnSettings()}
<ColumnButtonWrapper> <Flex
<StyledButtonWrapper> justify="space-between"
<Button align="center"
buttonSize="small" css={theme => ({
buttonStyle="tertiary" marginBottom: theme.sizeUnit * 4,
onClick={this.syncMetadata} })}
className="sync-from-source" >
disabled={this.state.isEditMode} <Input.Search
> placeholder={t('Search columns by name')}
<Icons.DatabaseOutlined iconSize="m" /> value={this.state.columnSearchTerm}
{t('Sync columns from source')} onChange={e =>
</Button> this.setState({ columnSearchTerm: e.target.value })
</StyledButtonWrapper> }
</ColumnButtonWrapper> css={theme => ({
<Input.Search width: theme.sizeUnit * 75,
placeholder={t('Search columns by name')} })}
value={this.state.columnSearchTerm} allowClear
onChange={e => />
this.setState({ columnSearchTerm: e.target.value }) <Button
} buttonSize="small"
style={{ marginBottom: 16, width: 300 }} buttonStyle="tertiary"
allowClear onClick={this.syncMetadata}
/> className="sync-from-source"
disabled={this.state.isEditMode}
>
<Icons.DatabaseOutlined iconSize="m" />
{t('Sync columns from source')}
</Button>
</Flex>
<ColumnCollectionTable <ColumnCollectionTable
className="columns-table" className="columns-table"
columns={this.state.databaseColumns} columns={this.state.databaseColumns}
@@ -2429,21 +2441,25 @@ class DatasourceEditor extends PureComponent<
children: ( children: (
<StyledTableTabWrapper> <StyledTableTabWrapper>
{this.renderDefaultColumnSettings()} {this.renderDefaultColumnSettings()}
<Input.Search
placeholder={t('Search calculated columns by name')}
value={this.state.calculatedColumnSearchTerm}
onChange={e =>
this.setState({
calculatedColumnSearchTerm: e.target.value,
})
}
style={{ marginBottom: 16, width: 300 }}
allowClear
/>
<ColumnCollectionTable <ColumnCollectionTable
columns={this.state.calculatedColumns} columns={this.state.calculatedColumns}
filterTerm={this.state.calculatedColumnSearchTerm} filterTerm={this.state.calculatedColumnSearchTerm}
filterFields={['column_name']} filterFields={['column_name']}
toolbarExtra={
<Input.Search
placeholder={t('Search calculated columns by name')}
value={this.state.calculatedColumnSearchTerm}
onChange={e =>
this.setState({
calculatedColumnSearchTerm: e.target.value,
})
}
css={theme => ({
width: theme.sizeUnit * 75,
})}
allowClear
/>
}
onColumnsChange={calculatedColumns => onColumnsChange={calculatedColumns =>
this.setColumns({ calculatedColumns }) this.setColumns({ calculatedColumns })
} }
@@ -2526,7 +2542,7 @@ class DatasourceEditor extends PureComponent<
key: TABS_KEYS.SETTINGS, key: TABS_KEYS.SETTINGS,
label: t('Settings'), label: t('Settings'),
children: ( children: (
<div style={{ overflowX: 'hidden' }}> <div css={{ overflow: 'hidden' }}>
<Row gutter={16}> <Row gutter={16}>
<Col xs={24} md={12}> <Col xs={24} md={12}>
<FormContainer> <FormContainer>

View File

@@ -53,6 +53,7 @@ export interface CRUDCollectionProps {
emptyMessage?: ReactNode; emptyMessage?: ReactNode;
expandFieldset?: ReactNode; expandFieldset?: ReactNode;
extraButtons?: ReactNode; extraButtons?: ReactNode;
toolbarExtra?: ReactNode;
itemGenerator?: () => any; itemGenerator?: () => any;
itemCellProps?: Record< itemCellProps?: Record<
string, string,