diff --git a/superset-frontend/src/components/Datasource/components/CollectionTable/index.tsx b/superset-frontend/src/components/Datasource/components/CollectionTable/index.tsx index a9bbda126a4b..01887b3adde1 100644 --- a/superset-frontend/src/components/Datasource/components/CollectionTable/index.tsx +++ b/superset-frontend/src/components/Datasource/components/CollectionTable/index.tsx @@ -53,10 +53,6 @@ const StyledButtonWrapper = styled.span` type CollectionItem = { id: string | number; [key: string]: any }; -function createCollectionArray(collection: Record) { - return Object.keys(collection).map(k => collection[k] as CollectionItem); -} - function createKeyedCollection(arr: Array) { const collectionArray = arr.map( (o: any) => @@ -109,6 +105,7 @@ export default class CRUDCollection extends PureComponent< const { collection, collectionArray } = createKeyedCollection( this.props.collection, ); + this.setState(prevState => ({ collection, collectionArray, @@ -197,7 +194,26 @@ export default class CRUDCollection extends PureComponent< } changeCollection(collection: any) { - const newCollectionArray = createCollectionArray(collection); + // Preserve existing order instead of recreating from Object.keys() + const existingIds = new Set( + this.state.collectionArray.map(item => item.id), + ); + const newCollectionArray: CollectionItem[] = []; + + // First pass: preserve existing order and update items + for (const existingItem of this.state.collectionArray) { + if (collection[existingItem.id]) { + newCollectionArray.push(collection[existingItem.id]); + } + } + + // Second pass: add new items + for (const item of Object.values(collection) as CollectionItem[]) { + if (!existingIds.has(item.id)) { + newCollectionArray.push(item); + } + } + this.setState({ collection, collectionArray: newCollectionArray }); if (this.props.onChange) { diff --git a/superset-frontend/src/components/Datasource/components/DatasourceEditor/DatasourceEditor.jsx b/superset-frontend/src/components/Datasource/components/DatasourceEditor/DatasourceEditor.jsx index 04f4f18ef34b..22ede5c41def 100644 --- a/superset-frontend/src/components/Datasource/components/DatasourceEditor/DatasourceEditor.jsx +++ b/superset-frontend/src/components/Datasource/components/DatasourceEditor/DatasourceEditor.jsx @@ -278,7 +278,7 @@ function ColumnCollectionTable({ label={t('SQL expression')} control={ @@ -691,11 +691,13 @@ class DatasourceEditor extends PureComponent { const { datasourceType, datasource } = this.state; const sql = datasourceType === DATASOURCE_TYPES.physical.key ? '' : datasource.sql; + const newDatasource = { ...this.state.datasource, sql, columns: [...this.state.databaseColumns, ...this.state.calculatedColumns], }; + this.props.onChange(newDatasource, this.state.errors); } @@ -1888,6 +1890,49 @@ class DatasourceEditor extends PureComponent { ); } + componentDidUpdate(prevProps) { + // Preserve calculated columns order when props change to prevent jumping + if (this.props.datasource !== prevProps.datasource) { + const newCalculatedColumns = this.props.datasource.columns.filter( + col => !!col.expression, + ); + const currentCalculatedColumns = this.state.calculatedColumns; + + if (newCalculatedColumns.length === currentCalculatedColumns.length) { + // Try to preserve the order by matching with existing calculated columns + const orderedCalculatedColumns = []; + const usedIds = new Set(); + + // First, add existing columns in their current order + currentCalculatedColumns.forEach(currentCol => { + const id = currentCol.id || currentCol.column_name; + const updatedCol = newCalculatedColumns.find( + newCol => (newCol.id || newCol.column_name) === id, + ); + if (updatedCol) { + orderedCalculatedColumns.push(updatedCol); + usedIds.add(id); + } + }); + + // Then add any new columns that weren't in the current list + newCalculatedColumns.forEach(newCol => { + const id = newCol.id || newCol.column_name; + if (!usedIds.has(id)) { + orderedCalculatedColumns.push(newCol); + } + }); + + this.setState({ + calculatedColumns: orderedCalculatedColumns, + databaseColumns: this.props.datasource.columns.filter( + col => !col.expression, + ), + }); + } + } + } + componentDidMount() { Mousetrap.bind('ctrl+shift+f', e => { e.preventDefault();