From ec747121385beacc7c3b7906f1468aaf1a558aac Mon Sep 17 00:00:00 2001 From: jayacryl <159848059+jayacryl@users.noreply.github.com> Date: Tue, 12 Nov 2024 16:34:07 -0800 Subject: [PATCH 1/2] fix(ingestion-web) sorting and filtering uses api --- .../source/ListIngestionSourcesResolver.java | 47 +++++++++------- .../src/main/resources/ingestion.graphql | 5 ++ .../ListIngestionSourceResolverTest.java | 2 +- ...kfillIngestionSourceInfoIndicesConfig.java | 29 ++++++++++ .../BackfillIngestionSourceInfoIndices.java | 43 ++++++++++++++ ...ackfillIngestionSourceInfoIndicesStep.java | 56 +++++++++++++++++++ .../app/ingest/source/IngestionSourceList.tsx | 31 +++++----- .../ingest/source/IngestionSourceTable.tsx | 18 ++++-- .../__tests__/IngestionSourceList.test.tsx | 38 ------------- .../ingestion/DataHubIngestionSourceInfo.pdl | 6 ++ .../src/main/resources/application.yaml | 5 ++ 11 files changed, 200 insertions(+), 80 deletions(-) create mode 100644 datahub-upgrade/src/main/java/com/linkedin/datahub/upgrade/config/BackfillIngestionSourceInfoIndicesConfig.java create mode 100644 datahub-upgrade/src/main/java/com/linkedin/datahub/upgrade/system/ingestion/BackfillIngestionSourceInfoIndices.java create mode 100644 datahub-upgrade/src/main/java/com/linkedin/datahub/upgrade/system/ingestion/BackfillIngestionSourceInfoIndicesStep.java delete mode 100644 datahub-web-react/src/app/ingest/source/__tests__/IngestionSourceList.test.tsx diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/ingest/source/ListIngestionSourcesResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/ingest/source/ListIngestionSourcesResolver.java index 8ead47aa65ceb..33b1555b73fab 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/ingest/source/ListIngestionSourcesResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/ingest/source/ListIngestionSourcesResolver.java @@ -15,20 +15,22 @@ import com.linkedin.entity.EntityResponse; import com.linkedin.entity.client.EntityClient; import com.linkedin.metadata.Constants; +import com.linkedin.metadata.query.filter.SortCriterion; +import com.linkedin.metadata.query.filter.SortOrder; import com.linkedin.metadata.search.SearchEntity; import com.linkedin.metadata.search.SearchResult; import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; -import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; /** Lists all ingestion sources stored within DataHub. Requires the MANAGE_INGESTION privilege. */ +@Slf4j public class ListIngestionSourcesResolver implements DataFetcher> { @@ -57,6 +59,22 @@ public CompletableFuture get( final List filters = input.getFilters() == null ? Collections.emptyList() : input.getFilters(); + // construct sort criteria, defaulting to systemCreated + final SortCriterion sortCriterion; + + // if query is expecting to sort by something, use that + final com.linkedin.datahub.graphql.generated.SortCriterion sortCriterionInput = + input.getSort(); + if (sortCriterionInput != null) { + sortCriterion = + new SortCriterion() + .setField(sortCriterionInput.getField()) + .setOrder(SortOrder.valueOf(sortCriterionInput.getSortOrder().name())); + } else { + // TODO: default to last executed + sortCriterion = null; + } + return GraphQLConcurrencyUtils.supplyAsync( () -> { try { @@ -69,33 +87,24 @@ public CompletableFuture get( Constants.INGESTION_SOURCE_ENTITY_NAME, query, buildFilter(filters, Collections.emptyList()), - null, + sortCriterion != null ? List.of(sortCriterion) : null, start, count); + final List entitiesUrnList = + gmsResult.getEntities().stream().map(SearchEntity::getEntity).toList(); // Then, resolve all ingestion sources final Map entities = _entityClient.batchGetV2( context.getOperationContext(), Constants.INGESTION_SOURCE_ENTITY_NAME, - new HashSet<>( - gmsResult.getEntities().stream() - .map(SearchEntity::getEntity) - .collect(Collectors.toList())), + new HashSet<>(entitiesUrnList), ImmutableSet.of( Constants.INGESTION_INFO_ASPECT_NAME, Constants.INGESTION_SOURCE_KEY_ASPECT_NAME)); - final Collection sortedEntities = - entities.values().stream() - .sorted( - Comparator.comparingLong( - s -> - -s.getAspects() - .get(Constants.INGESTION_SOURCE_KEY_ASPECT_NAME) - .getCreated() - .getTime())) - .collect(Collectors.toList()); + final List entitiesOrdered = + entitiesUrnList.stream().map(entities::get).filter(Objects::nonNull).toList(); // Now that we have entities we can bind this to a result. final ListIngestionSourcesResult result = new ListIngestionSourcesResult(); @@ -103,7 +112,7 @@ public CompletableFuture get( result.setCount(gmsResult.getPageSize()); result.setTotal(gmsResult.getNumEntities()); result.setIngestionSources( - IngestionResolverUtils.mapIngestionSources(sortedEntities)); + IngestionResolverUtils.mapIngestionSources(entitiesOrdered)); return result; } catch (Exception e) { diff --git a/datahub-graphql-core/src/main/resources/ingestion.graphql b/datahub-graphql-core/src/main/resources/ingestion.graphql index 77327ae6d4db1..719ffea30c3dd 100644 --- a/datahub-graphql-core/src/main/resources/ingestion.graphql +++ b/datahub-graphql-core/src/main/resources/ingestion.graphql @@ -448,6 +448,11 @@ input ListIngestionSourcesInput { Optional Facet filters to apply to the result set """ filters: [FacetFilterInput!] + + """ + Optional sort order. Defaults to use systemCreated. + """ + sort: SortCriterion } """ diff --git a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/ingest/source/ListIngestionSourceResolverTest.java b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/ingest/source/ListIngestionSourceResolverTest.java index 05428788dc3c9..dc22255b1537c 100644 --- a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/ingest/source/ListIngestionSourceResolverTest.java +++ b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/ingest/source/ListIngestionSourceResolverTest.java @@ -28,7 +28,7 @@ public class ListIngestionSourceResolverTest { private static final ListIngestionSourcesInput TEST_INPUT = - new ListIngestionSourcesInput(0, 20, null, null); + new ListIngestionSourcesInput(0, 20, null, null, null); @Test public void testGetSuccess() throws Exception { diff --git a/datahub-upgrade/src/main/java/com/linkedin/datahub/upgrade/config/BackfillIngestionSourceInfoIndicesConfig.java b/datahub-upgrade/src/main/java/com/linkedin/datahub/upgrade/config/BackfillIngestionSourceInfoIndicesConfig.java new file mode 100644 index 0000000000000..f525c4e35875d --- /dev/null +++ b/datahub-upgrade/src/main/java/com/linkedin/datahub/upgrade/config/BackfillIngestionSourceInfoIndicesConfig.java @@ -0,0 +1,29 @@ +package com.linkedin.datahub.upgrade.config; + +import com.linkedin.datahub.upgrade.system.NonBlockingSystemUpgrade; +import com.linkedin.datahub.upgrade.system.ingestion.BackfillIngestionSourceInfoIndices; +import com.linkedin.metadata.entity.AspectDao; +import com.linkedin.metadata.entity.EntityService; +import io.datahubproject.metadata.context.OperationContext; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; + +@Configuration +@Conditional(SystemUpdateCondition.NonBlockingSystemUpdateCondition.class) +public class BackfillIngestionSourceInfoIndicesConfig { + + @Bean + public NonBlockingSystemUpgrade backfillIngestionSourceInfoIndices( + final OperationContext opContext, + final EntityService entityService, + final AspectDao aspectDao, + @Value("${systemUpdate.ingestionIndices.enabled}") final boolean enabled, + @Value("${systemUpdate.ingestionIndices.batchSize}") final Integer batchSize, + @Value("${systemUpdate.ingestionIndices.delayMs}") final Integer delayMs, + @Value("${systemUpdate.ingestionIndices.limit}") final Integer limit) { + return new BackfillIngestionSourceInfoIndices( + opContext, entityService, aspectDao, enabled, batchSize, delayMs, limit); + } +} diff --git a/datahub-upgrade/src/main/java/com/linkedin/datahub/upgrade/system/ingestion/BackfillIngestionSourceInfoIndices.java b/datahub-upgrade/src/main/java/com/linkedin/datahub/upgrade/system/ingestion/BackfillIngestionSourceInfoIndices.java new file mode 100644 index 0000000000000..70f0844367f67 --- /dev/null +++ b/datahub-upgrade/src/main/java/com/linkedin/datahub/upgrade/system/ingestion/BackfillIngestionSourceInfoIndices.java @@ -0,0 +1,43 @@ +package com.linkedin.datahub.upgrade.system.ingestion; + +import com.google.common.collect.ImmutableList; +import com.linkedin.datahub.upgrade.UpgradeStep; +import com.linkedin.datahub.upgrade.system.NonBlockingSystemUpgrade; +import com.linkedin.metadata.entity.AspectDao; +import com.linkedin.metadata.entity.EntityService; +import io.datahubproject.metadata.context.OperationContext; +import java.util.List; +import javax.annotation.Nonnull; + +public class BackfillIngestionSourceInfoIndices implements NonBlockingSystemUpgrade { + + private final List _steps; + + public BackfillIngestionSourceInfoIndices( + @Nonnull OperationContext opContext, + EntityService entityService, + AspectDao aspectDao, + boolean enabled, + Integer batchSize, + Integer batchDelayMs, + Integer limit) { + if (enabled) { + _steps = + ImmutableList.of( + new BackfillIngestionSourceInfoIndicesStep( + opContext, entityService, aspectDao, batchSize, batchDelayMs, limit)); + } else { + _steps = ImmutableList.of(); + } + } + + @Override + public String id() { + return getClass().getSimpleName(); + } + + @Override + public List steps() { + return _steps; + } +} diff --git a/datahub-upgrade/src/main/java/com/linkedin/datahub/upgrade/system/ingestion/BackfillIngestionSourceInfoIndicesStep.java b/datahub-upgrade/src/main/java/com/linkedin/datahub/upgrade/system/ingestion/BackfillIngestionSourceInfoIndicesStep.java new file mode 100644 index 0000000000000..2525a57bfd7ec --- /dev/null +++ b/datahub-upgrade/src/main/java/com/linkedin/datahub/upgrade/system/ingestion/BackfillIngestionSourceInfoIndicesStep.java @@ -0,0 +1,56 @@ +package com.linkedin.datahub.upgrade.system.ingestion; + +import static com.linkedin.metadata.Constants.*; + +import com.linkedin.common.urn.Urn; +import com.linkedin.datahub.upgrade.system.AbstractMCLStep; +import com.linkedin.metadata.boot.BootstrapStep; +import com.linkedin.metadata.entity.AspectDao; +import com.linkedin.metadata.entity.EntityService; +import io.datahubproject.metadata.context.OperationContext; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class BackfillIngestionSourceInfoIndicesStep extends AbstractMCLStep { + + private static final String UPGRADE_ID = BackfillIngestionSourceInfoIndices.class.getSimpleName(); + private static final Urn UPGRADE_ID_URN = BootstrapStep.getUpgradeUrn(UPGRADE_ID); + + public BackfillIngestionSourceInfoIndicesStep( + @Nonnull OperationContext opContext, + EntityService entityService, + AspectDao aspectDao, + Integer batchSize, + Integer batchDelayMs, + Integer limit) { + super(opContext, entityService, aspectDao, batchSize, batchDelayMs, limit); + } + + @Override + public String id() { + return UPGRADE_ID; + } + + @Nonnull + @Override + protected String getAspectName() { + return INGESTION_INFO_ASPECT_NAME; + } + + @Nullable + @Override + protected String getUrnLike() { + return "urn:li:" + INGESTION_SOURCE_ENTITY_NAME + ":%"; + } + + /** + * Returns whether the upgrade should proceed if the step fails after exceeding the maximum + * retries. + */ + @Override + public boolean isOptional() { + return true; + } +} diff --git a/datahub-web-react/src/app/ingest/source/IngestionSourceList.tsx b/datahub-web-react/src/app/ingest/source/IngestionSourceList.tsx index d9ab4cdc499f5..ab07813fa2d47 100644 --- a/datahub-web-react/src/app/ingest/source/IngestionSourceList.tsx +++ b/datahub-web-react/src/app/ingest/source/IngestionSourceList.tsx @@ -17,7 +17,7 @@ import TabToolbar from '../../entity/shared/components/styled/TabToolbar'; import { IngestionSourceBuilderModal } from './builder/IngestionSourceBuilderModal'; import { addToListIngestionSourcesCache, CLI_EXECUTOR_ID, removeFromListIngestionSourcesCache } from './utils'; import { DEFAULT_EXECUTOR_ID, SourceBuilderState, StringMapEntryInput } from './builder/types'; -import { IngestionSource, UpdateIngestionSourceInput } from '../../../types.generated'; +import { IngestionSource, SortCriterion, SortOrder, UpdateIngestionSourceInput } from '../../../types.generated'; import { SearchBar } from '../../search/SearchBar'; import { useEntityRegistry } from '../../useEntityRegistry'; import { ExecutionDetailsModal } from './executions/ExecutionRequestDetailsModal'; @@ -60,16 +60,6 @@ export enum IngestionSourceType { CLI, } -export function shouldIncludeSource(source: any, sourceFilter: IngestionSourceType) { - if (sourceFilter === IngestionSourceType.CLI) { - return source.config.executorId === CLI_EXECUTOR_ID; - } - if (sourceFilter === IngestionSourceType.UI) { - return source.config.executorId !== CLI_EXECUTOR_ID; - } - return true; -} - const DEFAULT_PAGE_SIZE = 25; const removeExecutionsFromIngestionSource = (source) => { @@ -105,6 +95,7 @@ export const IngestionSourceList = () => { // Set of removed urns used to account for eventual consistency const [removedUrns, setRemovedUrns] = useState([]); const [sourceFilter, setSourceFilter] = useState(IngestionSourceType.ALL); + const [sort, setSort] = useState(); const [hideSystemSources, setHideSystemSources] = useState(true); /** @@ -115,7 +106,14 @@ export const IngestionSourceList = () => { // Ingestion Source Default Filters const filters = hideSystemSources ? [{ field: 'sourceType', values: [SYSTEM_INTERNAL_SOURCE_TYPE], negated: true }] - : undefined; + : []; + if (sourceFilter !== IngestionSourceType.ALL) { + filters.push({ + field: 'sourceExecutorId', + values: [CLI_EXECUTOR_ID], + negated: sourceFilter !== IngestionSourceType.CLI, + }); + } // Ingestion Source Queries const { loading, error, data, client, refetch } = useListIngestionSourcesQuery({ @@ -123,8 +121,9 @@ export const IngestionSourceList = () => { input: { start, count: pageSize, - query: (query?.length && query) || undefined, - filters, + query: query?.length ? query : undefined, + filters: filters.length ? filters : undefined, + sort, }, }, fetchPolicy: (query?.length || 0) > 0 ? 'no-cache' : 'cache-first', @@ -138,9 +137,7 @@ export const IngestionSourceList = () => { const totalSources = data?.listIngestionSources?.total || 0; const sources = data?.listIngestionSources?.ingestionSources || []; - const filteredSources = sources.filter( - (source) => !removedUrns.includes(source.urn) && shouldIncludeSource(source, sourceFilter), - ) as IngestionSource[]; + const filteredSources = sources.filter((source) => !removedUrns.includes(source.urn)) as IngestionSource[]; const focusSource = (focusSourceUrn && filteredSources.find((source) => source.urn === focusSourceUrn)) || undefined; diff --git a/datahub-web-react/src/app/ingest/source/IngestionSourceTable.tsx b/datahub-web-react/src/app/ingest/source/IngestionSourceTable.tsx index 00d04ed245edf..d890160958ed1 100644 --- a/datahub-web-react/src/app/ingest/source/IngestionSourceTable.tsx +++ b/datahub-web-react/src/app/ingest/source/IngestionSourceTable.tsx @@ -1,6 +1,7 @@ import { Empty, Typography } from 'antd'; import React from 'react'; import styled from 'styled-components/macro'; +import { SorterResult } from 'antd/lib/table/interface'; import { StyledTable } from '../../entity/shared/components/styled/StyledTable'; import { ANTD_GRAY } from '../../entity/shared/constants'; import { CLI_EXECUTOR_ID, getIngestionSourceStatus } from './utils'; @@ -31,6 +32,7 @@ interface Props { onView: (urn: string) => void; onDelete: (urn: string) => void; onRefresh: () => void; + onChangeSort: (field: string, order: SorterResult['order']) => void; } function IngestionSourceTable({ @@ -42,6 +44,7 @@ function IngestionSourceTable({ onView, onDelete, onRefresh, + onChangeSort, }: Props) { const tableColumns = [ { @@ -49,14 +52,14 @@ function IngestionSourceTable({ dataIndex: 'type', key: 'type', render: (type: string, record: any) => , - sorter: (sourceA, sourceB) => sourceA.type.localeCompare(sourceB.type), + sorter: true, }, { title: 'Name', dataIndex: 'name', key: 'name', render: (name: string) => name || '', - sorter: (sourceA, sourceB) => sourceA.name.localeCompare(sourceB.name), + sorter: true, }, { title: 'Schedule', @@ -69,14 +72,12 @@ function IngestionSourceTable({ dataIndex: 'execCount', key: 'execCount', render: (execCount: any) => {execCount || '0'}, - sorter: (sourceA, sourceB) => sourceA.execCount - sourceB.execCount, }, { title: 'Last Execution', dataIndex: 'lastExecTime', key: 'lastExecTime', render: LastExecutionColumn, - sorter: (sourceA, sourceB) => sourceA.lastExecTime - sourceB.lastExecTime, }, { title: 'Last Status', @@ -85,7 +86,6 @@ function IngestionSourceTable({ render: (status: any, record) => ( ), - sorter: (sourceA, sourceB) => (sourceA.lastExecStatus || '').localeCompare(sourceB.lastExecStatus || ''), }, { title: '', @@ -127,9 +127,17 @@ function IngestionSourceTable({ cliIngestion: source.config?.executorId === CLI_EXECUTOR_ID, })); + const handleTableChange = (_: any, __: any, sorter: any) => { + const sorterTyped: SorterResult = sorter; + const field = sorterTyped.field as string; + const { order } = sorterTyped; + onChangeSort(field, order); + }; + return ( (record.cliIngestion ? 'cliIngestion' : '')} diff --git a/datahub-web-react/src/app/ingest/source/__tests__/IngestionSourceList.test.tsx b/datahub-web-react/src/app/ingest/source/__tests__/IngestionSourceList.test.tsx deleted file mode 100644 index b057f8b24c980..0000000000000 --- a/datahub-web-react/src/app/ingest/source/__tests__/IngestionSourceList.test.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { IngestionSourceType, shouldIncludeSource } from '../IngestionSourceList'; -import { CLI_EXECUTOR_ID } from '../utils'; - -describe('shouldIncludeSource', () => { - it('should return true if the source filter is for CLI and the source is a CLI source', () => { - const source = { config: { executorId: CLI_EXECUTOR_ID } }; - const isSourceIncluded = shouldIncludeSource(source, IngestionSourceType.CLI); - expect(isSourceIncluded).toBe(true); - }); - - it('should return false if the source filter is for CLI and the source is not a CLI source', () => { - const source = { config: { executorId: 'default' } }; - const isSourceIncluded = shouldIncludeSource(source, IngestionSourceType.CLI); - expect(isSourceIncluded).toBe(false); - }); - - it('should return true if the source filter is for UI and the source is a UI source', () => { - const source = { config: { executorId: 'default' } }; - const isSourceIncluded = shouldIncludeSource(source, IngestionSourceType.UI); - expect(isSourceIncluded).toBe(true); - }); - - it('should return false if the source filter is for UI and the source is not a UI source', () => { - const source = { config: { executorId: CLI_EXECUTOR_ID } }; - const isSourceIncluded = shouldIncludeSource(source, IngestionSourceType.UI); - expect(isSourceIncluded).toBe(false); - }); - - it('should return true no matter what if the source type is all', () => { - const source1 = { config: { executorId: CLI_EXECUTOR_ID } }; - const isSourceIncluded1 = shouldIncludeSource(source1, IngestionSourceType.ALL); - expect(isSourceIncluded1).toBe(true); - - const source2 = { config: { executorId: 'default' } }; - const isSourceIncluded2 = shouldIncludeSource(source2, IngestionSourceType.ALL); - expect(isSourceIncluded2).toBe(true); - }); -}); diff --git a/metadata-models/src/main/pegasus/com/linkedin/ingestion/DataHubIngestionSourceInfo.pdl b/metadata-models/src/main/pegasus/com/linkedin/ingestion/DataHubIngestionSourceInfo.pdl index 37e85b6e542bd..3d384bbd6b08f 100644 --- a/metadata-models/src/main/pegasus/com/linkedin/ingestion/DataHubIngestionSourceInfo.pdl +++ b/metadata-models/src/main/pegasus/com/linkedin/ingestion/DataHubIngestionSourceInfo.pdl @@ -21,6 +21,9 @@ record DataHubIngestionSourceInfo { /** * The type of the source itself, e.g. mysql, bigquery, bigquery-usage. Should match the recipe. */ + @Searchable = { + "fieldType": "TEXT_PARTIAL" + } type: string /** @@ -50,6 +53,9 @@ record DataHubIngestionSourceInfo { /** * The id of the executor to use to execute the ingestion run */ + @Searchable = { + "fieldName": "sourceExecutorId" + } executorId: optional string /** diff --git a/metadata-service/configuration/src/main/resources/application.yaml b/metadata-service/configuration/src/main/resources/application.yaml index eb7bb2869584b..8010ae187b6c8 100644 --- a/metadata-service/configuration/src/main/resources/application.yaml +++ b/metadata-service/configuration/src/main/resources/application.yaml @@ -372,6 +372,11 @@ systemUpdate: batchSize: ${BOOTSTRAP_SYSTEM_UPDATE_BROWSE_PATHS_V2_BATCH_SIZE:5000} reprocess: enabled: ${REPROCESS_DEFAULT_BROWSE_PATHS_V2:false} + ingestionIndices: + enabled: ${BOOTSTRAP_SYSTEM_UPDATE_INGESTION_INDICES_ENABLED:true} + batchSize: ${BOOTSTRAP_SYSTEM_UPDATE_INGESTION_INDICES_BATCH_SIZE:5000} + delayMs: ${BOOTSTRAP_SYSTEM_UPDATE_INGESTION_INDICES_DELAY_MS:1000} + limit: ${BOOTSTRAP_SYSTEM_UPDATE_INGESTION_INDICES_CLL_LIMIT:0} policyFields: enabled: ${BOOTSTRAP_SYSTEM_UPDATE_POLICY_FIELDS_ENABLED:true} batchSize: ${BOOTSTRAP_SYSTEM_UPDATE_POLICY_FIELDS_BATCH_SIZE:5000} From b2e8bc0381d5ae2d929cd070d97a04b1179eaae1 Mon Sep 17 00:00:00 2001 From: jayacryl <159848059+jayacryl@users.noreply.github.com> Date: Tue, 12 Nov 2024 16:37:43 -0800 Subject: [PATCH 2/2] lint --- .../src/app/ingest/source/IngestionSourceList.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/datahub-web-react/src/app/ingest/source/IngestionSourceList.tsx b/datahub-web-react/src/app/ingest/source/IngestionSourceList.tsx index ab07813fa2d47..ccfa200fab630 100644 --- a/datahub-web-react/src/app/ingest/source/IngestionSourceList.tsx +++ b/datahub-web-react/src/app/ingest/source/IngestionSourceList.tsx @@ -373,6 +373,17 @@ export const IngestionSourceList = () => { setFocusSourceUrn(undefined); }; + const onChangeSort = (field, order) => { + setSort( + order + ? { + sortOrder: order === 'ascend' ? SortOrder.Ascending : SortOrder.Descending, + field, + } + : undefined, + ); + }; + return ( <> {!data && loading && } @@ -435,6 +446,7 @@ export const IngestionSourceList = () => { onView={onView} onDelete={onDelete} onRefresh={onRefresh} + onChangeSort={onChangeSort} />