diff --git a/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx b/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx index 32373301f6..72b22d4f28 100644 --- a/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx +++ b/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx @@ -154,6 +154,12 @@ const fakeDatabaseApiResult = { ], }; +const fakeDatabaseApiResultInReverseOrder = { + ...fakeDatabaseApiResult, + ids: [2, 1], + result: [...fakeDatabaseApiResult.result].reverse(), +}; + const fakeSchemaApiResult = { count: 2, result: ['information_schema', 'public'], @@ -168,7 +174,8 @@ const fakeFunctionNamesApiResult = { function_names: [], }; -const databaseApiRoute = 'glob:*/api/v1/database/?*'; +const databaseApiRoute = + 'glob:*/api/v1/database/?*order_column:database_name,order_direction*'; const catalogApiRoute = 'glob:*/api/v1/database/*/catalogs/?*'; const schemaApiRoute = 'glob:*/api/v1/database/*/schemas/?*'; const tablesApiRoute = 'glob:*/api/v1/database/*/tables/*'; @@ -241,6 +248,30 @@ test('Should database select display options', async () => { expect(await screen.findByText('test-mysql')).toBeInTheDocument(); }); +test('should display options in order of the api response', async () => { + fetchMock.get(databaseApiRoute, fakeDatabaseApiResultInReverseOrder, { + overwriteRoutes: true, + }); + const props = createProps(); + render(, { + useRedux: true, + store, + }); + const select = screen.getByRole('combobox', { + name: 'Select database or type to search databases', + }); + expect(select).toBeInTheDocument(); + userEvent.click(select); + const options = await screen.findAllByRole('option'); + + expect(options[0]).toHaveTextContent( + `${fakeDatabaseApiResultInReverseOrder.result[0].id}`, + ); + expect(options[1]).toHaveTextContent( + `${fakeDatabaseApiResultInReverseOrder.result[1].id}`, + ); +}); + test('Should fetch the search keyword when total count exceeds initial options', async () => { fetchMock.get( databaseApiRoute, diff --git a/superset-frontend/src/components/DatabaseSelector/index.tsx b/superset-frontend/src/components/DatabaseSelector/index.tsx index 14753c6dfe..308ebc34bd 100644 --- a/superset-frontend/src/components/DatabaseSelector/index.tsx +++ b/superset-frontend/src/components/DatabaseSelector/index.tsx @@ -16,8 +16,16 @@ * specific language governing permissions and limitations * under the License. */ -import React, { ReactNode, useState, useMemo, useEffect, useRef } from 'react'; +import React, { + ReactNode, + useState, + useMemo, + useEffect, + useRef, + useCallback, +} from 'react'; import { styled, SupersetClient, t } from '@superset-ui/core'; +import type { LabeledValue as AntdLabeledValue } from 'antd/lib/select'; import rison from 'rison'; import { AsyncSelect, Select } from 'src/components'; import Label from 'src/components/Label'; @@ -124,6 +132,10 @@ const SelectLabel = ({ const EMPTY_CATALOG_OPTIONS: CatalogOption[] = []; const EMPTY_SCHEMA_OPTIONS: SchemaOption[] = []; +interface AntdLabeledValueWithOrder extends AntdLabeledValue { + order: number; +} + export default function DatabaseSelector({ db, formMode = false, @@ -153,6 +165,11 @@ export default function DatabaseSelector({ const schemaRef = useRef(schema); schemaRef.current = schema; const { addSuccessToast } = useToasts(); + const sortComparator = useCallback( + (itemA: AntdLabeledValueWithOrder, itemB: AntdLabeledValueWithOrder) => + itemA.order - itemB.order, + [], + ); const loadDatabases = useMemo( () => @@ -165,7 +182,7 @@ export default function DatabaseSelector({ totalCount: number; }> => { const queryParams = rison.encode({ - order_columns: 'database_name', + order_column: 'database_name', order_direction: 'asc', page, page_size: pageSize, @@ -191,7 +208,8 @@ export default function DatabaseSelector({ if (result.length === 0) { if (onEmptyResults) onEmptyResults(search); } - const options = result.map((row: DatabaseObject) => ({ + + const options = result.map((row: DatabaseObject, order: number) => ({ label: ( , null, );