feat(ui): Adding Search Bar to all List Views (groups, users, domains, policies, ingestion) (#4919)

This commit is contained in:
John Joyce 2022-05-16 13:24:56 -07:00 committed by GitHub
parent a0e7262b00
commit f18f619a2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 340 additions and 79 deletions

View File

@ -1,6 +1,5 @@
package com.linkedin.datahub.graphql.resolvers.domain;
import com.linkedin.common.UrnArray;
import com.linkedin.common.urn.Urn;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
@ -11,13 +10,15 @@ import com.linkedin.datahub.graphql.generated.ListDomainsInput;
import com.linkedin.datahub.graphql.generated.ListDomainsResult;
import com.linkedin.entity.client.EntityClient;
import com.linkedin.metadata.Constants;
import com.linkedin.metadata.query.ListResult;
import com.linkedin.metadata.search.SearchEntity;
import com.linkedin.metadata.search.SearchResult;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.*;
@ -29,6 +30,7 @@ public class ListDomainsResolver implements DataFetcher<CompletableFuture<ListDo
private static final Integer DEFAULT_START = 0;
private static final Integer DEFAULT_COUNT = 20;
private static final String DEFAULT_QUERY = "";
private final EntityClient _entityClient;
@ -47,11 +49,13 @@ public class ListDomainsResolver implements DataFetcher<CompletableFuture<ListDo
final ListDomainsInput input = bindArgument(environment.getArgument("input"), ListDomainsInput.class);
final Integer start = input.getStart() == null ? DEFAULT_START : input.getStart();
final Integer count = input.getCount() == null ? DEFAULT_COUNT : input.getCount();
final String query = input.getQuery() == null ? DEFAULT_QUERY : input.getQuery();
try {
// First, get all group Urns.
final ListResult gmsResult = _entityClient.list(
final SearchResult gmsResult = _entityClient.search(
Constants.DOMAIN_ENTITY_NAME,
query,
Collections.emptyMap(),
start,
count,
@ -59,10 +63,12 @@ public class ListDomainsResolver implements DataFetcher<CompletableFuture<ListDo
// Now that we have entities we can bind this to a result.
final ListDomainsResult result = new ListDomainsResult();
result.setStart(gmsResult.getStart());
result.setCount(gmsResult.getCount());
result.setTotal(gmsResult.getTotal());
result.setDomains(mapUnresolvedDomains(gmsResult.getEntities()));
result.setStart(gmsResult.getFrom());
result.setCount(gmsResult.getPageSize());
result.setTotal(gmsResult.getNumEntities());
result.setDomains(mapUnresolvedDomains(gmsResult.getEntities().stream()
.map(SearchEntity::getEntity)
.collect(Collectors.toList())));
return result;
} catch (Exception e) {
throw new RuntimeException("Failed to list domains", e);
@ -73,7 +79,7 @@ public class ListDomainsResolver implements DataFetcher<CompletableFuture<ListDo
}
// This method maps urns returned from the list endpoint into Partial Domain objects which will be resolved be a separate Batch resolver.
private List<Domain> mapUnresolvedDomains(final UrnArray entityUrns) {
private List<Domain> mapUnresolvedDomains(final List<Urn> entityUrns) {
final List<Domain> results = new ArrayList<>();
for (final Urn urn : entityUrns) {
final Domain unresolvedDomain = new Domain();

View File

@ -10,7 +10,8 @@ import com.linkedin.datahub.graphql.generated.ListGroupsResult;
import com.linkedin.datahub.graphql.types.corpgroup.mappers.CorpGroupMapper;
import com.linkedin.entity.EntityResponse;
import com.linkedin.entity.client.EntityClient;
import com.linkedin.metadata.query.ListResult;
import com.linkedin.metadata.search.SearchEntity;
import com.linkedin.metadata.search.SearchResult;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import java.util.Collection;
@ -29,6 +30,7 @@ public class ListGroupsResolver implements DataFetcher<CompletableFuture<ListGro
private static final Integer DEFAULT_START = 0;
private static final Integer DEFAULT_COUNT = 20;
private static final String DEFAULT_QUERY = "";
private final EntityClient _entityClient;
@ -45,22 +47,25 @@ public class ListGroupsResolver implements DataFetcher<CompletableFuture<ListGro
final ListGroupsInput input = bindArgument(environment.getArgument("input"), ListGroupsInput.class);
final Integer start = input.getStart() == null ? DEFAULT_START : input.getStart();
final Integer count = input.getCount() == null ? DEFAULT_COUNT : input.getCount();
final String query = input.getQuery() == null ? DEFAULT_QUERY : input.getQuery();
return CompletableFuture.supplyAsync(() -> {
try {
// First, get all group Urns.
final ListResult gmsResult =
_entityClient.list(CORP_GROUP_ENTITY_NAME, Collections.emptyMap(), start, count, context.getAuthentication());
final SearchResult gmsResult =
_entityClient.search(CORP_GROUP_ENTITY_NAME, query, Collections.emptyMap(), start, count, context.getAuthentication());
// Then, get hydrate all groups.
final Map<Urn, EntityResponse> entities = _entityClient.batchGetV2(CORP_GROUP_ENTITY_NAME,
new HashSet<>(gmsResult.getEntities()), null, context.getAuthentication());
new HashSet<>(gmsResult.getEntities().stream()
.map(SearchEntity::getEntity)
.collect(Collectors.toList())), null, context.getAuthentication());
// Now that we have entities we can bind this to a result.
final ListGroupsResult result = new ListGroupsResult();
result.setStart(gmsResult.getStart());
result.setCount(gmsResult.getCount());
result.setTotal(gmsResult.getTotal());
result.setStart(gmsResult.getFrom());
result.setCount(gmsResult.getPageSize());
result.setTotal(gmsResult.getNumEntities());
result.setGroups(mapEntities(entities.values()));
return result;
} catch (Exception e) {

View File

@ -14,7 +14,8 @@ import com.linkedin.entity.EnvelopedAspect;
import com.linkedin.entity.EnvelopedAspectMap;
import com.linkedin.entity.client.EntityClient;
import com.linkedin.metadata.Constants;
import com.linkedin.metadata.query.ListResult;
import com.linkedin.metadata.search.SearchEntity;
import com.linkedin.metadata.search.SearchResult;
import com.linkedin.secret.DataHubSecretValue;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
@ -25,6 +26,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.*;
@ -38,6 +40,7 @@ public class ListSecretsResolver implements DataFetcher<CompletableFuture<ListSe
private static final Integer DEFAULT_START = 0;
private static final Integer DEFAULT_COUNT = 20;
private static final String DEFAULT_QUERY = "";
private final EntityClient _entityClient;
@ -54,24 +57,28 @@ public class ListSecretsResolver implements DataFetcher<CompletableFuture<ListSe
final ListSecretsInput input = bindArgument(environment.getArgument("input"), ListSecretsInput.class);
final Integer start = input.getStart() == null ? DEFAULT_START : input.getStart();
final Integer count = input.getCount() == null ? DEFAULT_COUNT : input.getCount();
final String query = input.getQuery() == null ? DEFAULT_QUERY : input.getQuery();
return CompletableFuture.supplyAsync(() -> {
try {
// First, get all secrets
final ListResult gmsResult = _entityClient.list(Constants.SECRETS_ENTITY_NAME, Collections.emptyMap(), start, count, context.getAuthentication());
final SearchResult
gmsResult = _entityClient.search(Constants.SECRETS_ENTITY_NAME, query, Collections.emptyMap(), start, count, context.getAuthentication());
// Then, resolve all secrets
final Map<Urn, EntityResponse> entities = _entityClient.batchGetV2(
Constants.SECRETS_ENTITY_NAME,
new HashSet<>(gmsResult.getEntities()),
new HashSet<>(gmsResult.getEntities().stream()
.map(SearchEntity::getEntity)
.collect(Collectors.toList())),
ImmutableSet.of(Constants.SECRET_VALUE_ASPECT_NAME),
context.getAuthentication());
// Now that we have entities we can bind this to a result.
final ListSecretsResult result = new ListSecretsResult();
result.setStart(gmsResult.getStart());
result.setCount(gmsResult.getCount());
result.setTotal(gmsResult.getTotal());
result.setStart(gmsResult.getFrom());
result.setCount(gmsResult.getPageSize());
result.setTotal(gmsResult.getNumEntities());
result.setSecrets(mapEntities(entities.values()));
return result;

View File

@ -11,13 +11,15 @@ import com.linkedin.datahub.graphql.resolvers.ingest.IngestionResolverUtils;
import com.linkedin.entity.EntityResponse;
import com.linkedin.entity.client.EntityClient;
import com.linkedin.metadata.Constants;
import com.linkedin.metadata.query.ListResult;
import com.linkedin.metadata.search.SearchEntity;
import com.linkedin.metadata.search.SearchResult;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.*;
@ -28,6 +30,7 @@ public class ListIngestionSourcesResolver implements DataFetcher<CompletableFutu
private static final Integer DEFAULT_START = 0;
private static final Integer DEFAULT_COUNT = 20;
private static final String DEFAULT_QUERY = "";
private final EntityClient _entityClient;
@ -44,12 +47,14 @@ public class ListIngestionSourcesResolver implements DataFetcher<CompletableFutu
final ListIngestionSourcesInput input = bindArgument(environment.getArgument("input"), ListIngestionSourcesInput.class);
final Integer start = input.getStart() == null ? DEFAULT_START : input.getStart();
final Integer count = input.getCount() == null ? DEFAULT_COUNT : input.getCount();
final String query = input.getQuery() == null ? DEFAULT_QUERY : input.getQuery();
return CompletableFuture.supplyAsync(() -> {
try {
// First, get all ingestion sources Urns.
final ListResult gmsResult = _entityClient.list(
final SearchResult gmsResult = _entityClient.search(
Constants.INGESTION_SOURCE_ENTITY_NAME,
query,
Collections.emptyMap(),
start,
count,
@ -58,15 +63,17 @@ public class ListIngestionSourcesResolver implements DataFetcher<CompletableFutu
// Then, resolve all ingestion sources
final Map<Urn, EntityResponse> entities = _entityClient.batchGetV2(
Constants.INGESTION_SOURCE_ENTITY_NAME,
new HashSet<>(gmsResult.getEntities()),
new HashSet<>(gmsResult.getEntities().stream()
.map(SearchEntity::getEntity)
.collect(Collectors.toList())),
ImmutableSet.of(Constants.INGESTION_INFO_ASPECT_NAME),
context.getAuthentication());
// Now that we have entities we can bind this to a result.
final ListIngestionSourcesResult result = new ListIngestionSourcesResult();
result.setStart(gmsResult.getStart());
result.setCount(gmsResult.getCount());
result.setTotal(gmsResult.getTotal());
result.setStart(gmsResult.getFrom());
result.setCount(gmsResult.getPageSize());
result.setTotal(gmsResult.getNumEntities());
result.setIngestionSources(IngestionResolverUtils.mapIngestionSources(entities.values()));
return result;

View File

@ -21,6 +21,7 @@ public class ListPoliciesResolver implements DataFetcher<CompletableFuture<ListP
private static final Integer DEFAULT_START = 0;
private static final Integer DEFAULT_COUNT = 20;
private static final String DEFAULT_QUERY = "";
private final PolicyFetcher _policyFetcher;
@ -37,12 +38,13 @@ public class ListPoliciesResolver implements DataFetcher<CompletableFuture<ListP
final ListPoliciesInput input = bindArgument(environment.getArgument("input"), ListPoliciesInput.class);
final Integer start = input.getStart() == null ? DEFAULT_START : input.getStart();
final Integer count = input.getCount() == null ? DEFAULT_COUNT : input.getCount();
final String query = input.getQuery() == null ? DEFAULT_QUERY : input.getQuery();
return CompletableFuture.supplyAsync(() -> {
try {
// First, get all policy Urns.
final PolicyFetcher.PolicyFetchResult policyFetchResult =
_policyFetcher.fetchPolicies(start, count, context.getAuthentication());
_policyFetcher.fetchPolicies(start, count, query, context.getAuthentication());
// Now that we have entities we can bind this to a result.
final ListPoliciesResult result = new ListPoliciesResult();

View File

@ -10,7 +10,8 @@ import com.linkedin.datahub.graphql.generated.ListUsersResult;
import com.linkedin.datahub.graphql.types.corpuser.mappers.CorpUserMapper;
import com.linkedin.entity.EntityResponse;
import com.linkedin.entity.client.EntityClient;
import com.linkedin.metadata.query.ListResult;
import com.linkedin.metadata.search.SearchEntity;
import com.linkedin.metadata.search.SearchResult;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import java.util.Collection;
@ -29,6 +30,7 @@ public class ListUsersResolver implements DataFetcher<CompletableFuture<ListUser
private static final Integer DEFAULT_START = 0;
private static final Integer DEFAULT_COUNT = 20;
private static final String DEFAULT_QUERY = "";
private final EntityClient _entityClient;
@ -45,22 +47,26 @@ public class ListUsersResolver implements DataFetcher<CompletableFuture<ListUser
final ListUsersInput input = bindArgument(environment.getArgument("input"), ListUsersInput.class);
final Integer start = input.getStart() == null ? DEFAULT_START : input.getStart();
final Integer count = input.getCount() == null ? DEFAULT_COUNT : input.getCount();
final String query = input.getQuery() == null ? DEFAULT_QUERY : input.getQuery();
return CompletableFuture.supplyAsync(() -> {
try {
// First, get all policy Urns.
final ListResult gmsResult =
_entityClient.list(CORP_USER_ENTITY_NAME, Collections.emptyMap(), start, count, context.getAuthentication());
final SearchResult gmsResult =
_entityClient.search(CORP_USER_ENTITY_NAME, query, Collections.emptyMap(), start, count, context.getAuthentication());
// Then, get hydrate all users.
final Map<Urn, EntityResponse> entities = _entityClient.batchGetV2(CORP_USER_ENTITY_NAME,
new HashSet<>(gmsResult.getEntities()), null, context.getAuthentication());
new HashSet<>(gmsResult.getEntities().stream()
.map(SearchEntity::getEntity)
.collect(Collectors.toList())
), null, context.getAuthentication());
// Now that we have entities we can bind this to a result.
final ListUsersResult result = new ListUsersResult();
result.setStart(gmsResult.getStart());
result.setCount(gmsResult.getCount());
result.setTotal(gmsResult.getTotal());
result.setStart(gmsResult.getFrom());
result.setCount(gmsResult.getPageSize());
result.setTotal(gmsResult.getNumEntities());
result.setUsers(mapEntities(entities.values()));
return result;
} catch (Exception e) {

View File

@ -6336,6 +6336,11 @@ input ListPoliciesInput {
The maximum number of Policies to be returned in the result set
"""
count: Int
"""
Optional search query
"""
query: String
}
"""
@ -6416,6 +6421,11 @@ input ListUsersInput {
The maximum number of Policies to be returned in the result set
"""
count: Int
"""
Optional search query
"""
query: String
}
"""
@ -6456,6 +6466,11 @@ input ListGroupsInput {
The maximum number of Policies to be returned in the result set
"""
count: Int
"""
Optional search query
"""
query: String
}
type EntityCountResults {
@ -7577,6 +7592,11 @@ input ListDomainsInput {
The maximum number of Domains to be returned in the result set
"""
count: Int
"""
Optional search query
"""
query: String
}
"""

View File

@ -188,6 +188,11 @@ input ListSecretsInput {
The number of results to be returned
"""
count: Int
"""
An optional search query
"""
query: String
}
"""
@ -354,6 +359,11 @@ input ListIngestionSourcesInput {
The number of results to be returned
"""
count: Int
"""
An optional search query
"""
query: String
}
"""

View File

@ -2,13 +2,14 @@ package com.linkedin.datahub.graphql.resolvers.domain;
import com.datahub.authentication.Authentication;
import com.google.common.collect.ImmutableSet;
import com.linkedin.common.UrnArray;
import com.linkedin.common.urn.Urn;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.generated.ListDomainsInput;
import com.linkedin.entity.client.EntityClient;
import com.linkedin.metadata.Constants;
import com.linkedin.metadata.query.ListResult;
import com.linkedin.metadata.search.SearchEntity;
import com.linkedin.metadata.search.SearchEntityArray;
import com.linkedin.metadata.search.SearchResult;
import com.linkedin.r2.RemoteInvocationException;
import graphql.schema.DataFetchingEnvironment;
import java.util.Collections;
@ -25,7 +26,7 @@ public class ListDomainsResolverTest {
private static final Urn TEST_DOMAIN_URN = Urn.createFromTuple("domain", "test-id");
private static final ListDomainsInput TEST_INPUT = new ListDomainsInput(
0, 20
0, 20, null
);
@Test
@ -33,18 +34,19 @@ public class ListDomainsResolverTest {
// Create resolver
EntityClient mockClient = Mockito.mock(EntityClient.class);
Mockito.when(mockClient.list(
Mockito.when(mockClient.search(
Mockito.eq(Constants.DOMAIN_ENTITY_NAME),
Mockito.eq(""),
Mockito.eq(Collections.emptyMap()),
Mockito.eq(0),
Mockito.eq(20),
Mockito.any(Authentication.class)
)).thenReturn(
new ListResult()
.setStart(0)
.setCount(1)
.setTotal(1)
.setEntities(new UrnArray(ImmutableSet.of(TEST_DOMAIN_URN)))
new SearchResult()
.setFrom(0)
.setPageSize(1)
.setNumEntities(1)
.setEntities(new SearchEntityArray(ImmutableSet.of(new SearchEntity().setEntity(TEST_DOMAIN_URN))))
);
ListDomainsResolver resolver = new ListDomainsResolver(mockClient);
@ -77,8 +79,9 @@ public class ListDomainsResolverTest {
Mockito.when(mockEnv.getContext()).thenReturn(mockContext);
assertThrows(CompletionException.class, () -> resolver.get(mockEnv).join());
Mockito.verify(mockClient, Mockito.times(0)).list(
Mockito.verify(mockClient, Mockito.times(0)).search(
Mockito.any(),
Mockito.eq("*"),
Mockito.anyMap(),
Mockito.anyInt(),
Mockito.anyInt(),
@ -89,8 +92,9 @@ public class ListDomainsResolverTest {
public void testGetEntityClientException() throws Exception {
// Create resolver
EntityClient mockClient = Mockito.mock(EntityClient.class);
Mockito.doThrow(RemoteInvocationException.class).when(mockClient).list(
Mockito.doThrow(RemoteInvocationException.class).when(mockClient).search(
Mockito.any(),
Mockito.eq(""),
Mockito.anyMap(),
Mockito.anyInt(),
Mockito.anyInt(),

View File

@ -3,7 +3,6 @@ package com.linkedin.datahub.graphql.resolvers.ingest.secret;
import com.datahub.authentication.Authentication;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.linkedin.common.UrnArray;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.generated.ListSecretsInput;
import com.linkedin.entity.Aspect;
@ -12,7 +11,9 @@ import com.linkedin.entity.EnvelopedAspect;
import com.linkedin.entity.EnvelopedAspectMap;
import com.linkedin.entity.client.EntityClient;
import com.linkedin.metadata.Constants;
import com.linkedin.metadata.query.ListResult;
import com.linkedin.metadata.search.SearchEntity;
import com.linkedin.metadata.search.SearchEntityArray;
import com.linkedin.metadata.search.SearchResult;
import com.linkedin.r2.RemoteInvocationException;
import com.linkedin.secret.DataHubSecretValue;
import graphql.schema.DataFetchingEnvironment;
@ -28,7 +29,7 @@ import static org.testng.Assert.*;
public class ListSecretsResolverTest {
private static final ListSecretsInput TEST_INPUT = new ListSecretsInput(
0, 20
0, 20, null
);
@Test
@ -38,18 +39,19 @@ public class ListSecretsResolverTest {
DataHubSecretValue returnedValue = getTestSecretValue();
Mockito.when(mockClient.list(
Mockito.when(mockClient.search(
Mockito.eq(Constants.SECRETS_ENTITY_NAME),
Mockito.eq(""),
Mockito.eq(Collections.emptyMap()),
Mockito.eq(0),
Mockito.eq(20),
Mockito.any(Authentication.class)
)).thenReturn(
new ListResult()
.setStart(0)
.setCount(1)
.setTotal(1)
.setEntities(new UrnArray(ImmutableSet.of(TEST_SECRET_URN)))
new SearchResult()
.setFrom(0)
.setPageSize(1)
.setNumEntities(1)
.setEntities(new SearchEntityArray(ImmutableSet.of(new SearchEntity().setEntity(TEST_SECRET_URN))))
);
Mockito.when(mockClient.batchGetV2(
@ -104,8 +106,9 @@ public class ListSecretsResolverTest {
Mockito.anySet(),
Mockito.anySet(),
Mockito.any(Authentication.class));
Mockito.verify(mockClient, Mockito.times(0)).list(
Mockito.verify(mockClient, Mockito.times(0)).search(
Mockito.any(),
Mockito.eq(""),
Mockito.anyMap(),
Mockito.anyInt(),
Mockito.anyInt(),

View File

@ -3,7 +3,6 @@ package com.linkedin.datahub.graphql.resolvers.ingest.source;
import com.datahub.authentication.Authentication;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.linkedin.common.UrnArray;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.generated.ListIngestionSourcesInput;
import com.linkedin.entity.Aspect;
@ -13,7 +12,9 @@ import com.linkedin.entity.EnvelopedAspectMap;
import com.linkedin.entity.client.EntityClient;
import com.linkedin.ingestion.DataHubIngestionSourceInfo;
import com.linkedin.metadata.Constants;
import com.linkedin.metadata.query.ListResult;
import com.linkedin.metadata.search.SearchEntity;
import com.linkedin.metadata.search.SearchEntityArray;
import com.linkedin.metadata.search.SearchResult;
import com.linkedin.r2.RemoteInvocationException;
import graphql.schema.DataFetchingEnvironment;
import java.util.Collections;
@ -27,7 +28,7 @@ import static org.testng.Assert.*;
public class ListIngestionSourceResolverTest {
private static final ListIngestionSourcesInput TEST_INPUT = new ListIngestionSourcesInput(0, 20);
private static final ListIngestionSourcesInput TEST_INPUT = new ListIngestionSourcesInput(0, 20, null);
@Test
public void testGetSuccess() throws Exception {
@ -36,18 +37,19 @@ public class ListIngestionSourceResolverTest {
DataHubIngestionSourceInfo returnedInfo = getTestIngestionSourceInfo();
Mockito.when(mockClient.list(
Mockito.when(mockClient.search(
Mockito.eq(Constants.INGESTION_SOURCE_ENTITY_NAME),
Mockito.eq(""),
Mockito.eq(Collections.emptyMap()),
Mockito.eq(0),
Mockito.eq(20),
Mockito.any(Authentication.class)
)).thenReturn(
new ListResult()
.setStart(0)
.setCount(1)
.setTotal(1)
.setEntities(new UrnArray(ImmutableSet.of(TEST_INGESTION_SOURCE_URN)))
new SearchResult()
.setFrom(0)
.setPageSize(1)
.setNumEntities(1)
.setEntities(new SearchEntityArray(ImmutableSet.of(new SearchEntity().setEntity(TEST_INGESTION_SOURCE_URN))))
);
Mockito.when(mockClient.batchGetV2(
@ -102,8 +104,9 @@ public class ListIngestionSourceResolverTest {
Mockito.anySet(),
Mockito.anySet(),
Mockito.any(Authentication.class));
Mockito.verify(mockClient, Mockito.times(0)).list(
Mockito.verify(mockClient, Mockito.times(0)).search(
Mockito.any(),
Mockito.eq(""),
Mockito.anyMap(),
Mockito.anyInt(),
Mockito.anyInt(),

View File

@ -1,6 +1,8 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { Button, Empty, List, message, Pagination, Typography } from 'antd';
import { useLocation } from 'react-router';
import styled from 'styled-components';
import * as QueryString from 'query-string';
import { PlusOutlined } from '@ant-design/icons';
import { Domain } from '../../types.generated';
import { useListDomainsQuery } from '../../graphql/domain.generated';
@ -8,6 +10,8 @@ import CreateDomainModal from './CreateDomainModal';
import { Message } from '../shared/Message';
import TabToolbar from '../entity/shared/components/styled/TabToolbar';
import DomainListItem from './DomainListItem';
import { SearchBar } from '../search/SearchBar';
import { useEntityRegistry } from '../useEntityRegistry';
const DomainsContainer = styled.div``;
@ -37,6 +41,13 @@ const PaginationInfo = styled(Typography.Text)`
const DEFAULT_PAGE_SIZE = 25;
export const DomainsList = () => {
const entityRegistry = useEntityRegistry();
const location = useLocation();
const params = QueryString.parse(location.search, { arrayFormat: 'comma' });
const paramsQuery = (params?.query as string) || undefined;
const [query, setQuery] = useState<undefined | string>(undefined);
useEffect(() => setQuery(paramsQuery), [paramsQuery]);
const [page, setPage] = useState(1);
const [isCreatingDomain, setIsCreatingDomain] = useState(false);
@ -48,6 +59,7 @@ export const DomainsList = () => {
input: {
start,
count: pageSize,
query,
},
},
fetchPolicy: 'no-cache',
@ -76,6 +88,22 @@ export const DomainsList = () => {
<PlusOutlined /> New Domain
</Button>
</div>
<SearchBar
initialQuery={query || ''}
placeholderText="Search domains..."
suggestions={[]}
style={{
maxWidth: 220,
padding: 0,
}}
inputStyle={{
height: 32,
fontSize: 12,
}}
onSearch={() => null}
onQueryChange={(q) => setQuery(q)}
entityRegistry={entityRegistry}
/>
</TabToolbar>
<DomainsStyledList
bordered

View File

@ -1,6 +1,8 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { Button, Empty, List, message, Pagination } from 'antd';
import styled from 'styled-components';
import { useLocation } from 'react-router';
import * as QueryString from 'query-string';
import { UsergroupAddOutlined } from '@ant-design/icons';
import { CorpGroup } from '../../../types.generated';
import { Message } from '../../shared/Message';
@ -8,6 +10,8 @@ import { useListGroupsQuery } from '../../../graphql/group.generated';
import GroupListItem from './GroupListItem';
import TabToolbar from '../../entity/shared/components/styled/TabToolbar';
import CreateGroupModal from './CreateGroupModal';
import { SearchBar } from '../../search/SearchBar';
import { useEntityRegistry } from '../../useEntityRegistry';
const GroupContainer = styled.div``;
@ -26,6 +30,13 @@ const GroupPaginationContainer = styled.div`
const DEFAULT_PAGE_SIZE = 25;
export const GroupList = () => {
const entityRegistry = useEntityRegistry();
const location = useLocation();
const params = QueryString.parse(location.search, { arrayFormat: 'comma' });
const paramsQuery = (params?.query as string) || undefined;
const [query, setQuery] = useState<undefined | string>(undefined);
useEffect(() => setQuery(paramsQuery), [paramsQuery]);
const [page, setPage] = useState(1);
const [isCreatingGroup, setIsCreatingGroup] = useState(false);
const [removedUrns, setRemovedUrns] = useState<string[]>([]);
@ -39,6 +50,7 @@ export const GroupList = () => {
input: {
start,
count: pageSize,
query,
},
},
fetchPolicy: 'no-cache',
@ -72,6 +84,22 @@ export const GroupList = () => {
<UsergroupAddOutlined /> Create group
</Button>
</div>
<SearchBar
initialQuery={query || ''}
placeholderText="Search groups..."
suggestions={[]}
style={{
maxWidth: 220,
padding: 0,
}}
inputStyle={{
height: 32,
fontSize: 12,
}}
onSearch={() => null}
onQueryChange={(q) => setQuery(q)}
entityRegistry={entityRegistry}
/>
</TabToolbar>
<GroupStyledList
bordered

View File

@ -1,10 +1,15 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { Empty, List, message, Pagination } from 'antd';
import styled from 'styled-components';
import * as QueryString from 'query-string';
import { useLocation } from 'react-router';
import UserListItem from './UserListItem';
import { Message } from '../../shared/Message';
import { useListUsersQuery } from '../../../graphql/user.generated';
import { CorpUser } from '../../../types.generated';
import TabToolbar from '../../entity/shared/components/styled/TabToolbar';
import { SearchBar } from '../../search/SearchBar';
import { useEntityRegistry } from '../../useEntityRegistry';
const UserContainer = styled.div``;
@ -23,6 +28,13 @@ const UserPaginationContainer = styled.div`
const DEFAULT_PAGE_SIZE = 25;
export const UserList = () => {
const entityRegistry = useEntityRegistry();
const location = useLocation();
const params = QueryString.parse(location.search, { arrayFormat: 'comma' });
const paramsQuery = (params?.query as string) || undefined;
const [query, setQuery] = useState<undefined | string>(undefined);
useEffect(() => setQuery(paramsQuery), [paramsQuery]);
const [page, setPage] = useState(1);
const [removedUrns, setRemovedUrns] = useState<string[]>([]);
@ -34,6 +46,7 @@ export const UserList = () => {
input: {
start,
count: pageSize,
query,
},
},
fetchPolicy: 'no-cache',
@ -61,6 +74,27 @@ export const UserList = () => {
{!data && loading && <Message type="loading" content="Loading users..." />}
{error && message.error('Failed to load users :(')}
<UserContainer>
<TabToolbar>
<div>
<></>
</div>
<SearchBar
initialQuery={query || ''}
placeholderText="Search users..."
suggestions={[]}
style={{
maxWidth: 220,
padding: 0,
}}
inputStyle={{
height: 32,
fontSize: 12,
}}
onSearch={() => null}
onQueryChange={(q) => setQuery(q)}
entityRegistry={entityRegistry}
/>
</TabToolbar>
<UserStyledList
bordered
locale={{

View File

@ -1,6 +1,8 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { Button, Empty, message, Modal, Pagination, Typography } from 'antd';
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
import * as QueryString from 'query-string';
import { useLocation } from 'react-router';
import styled from 'styled-components';
import {
useCreateSecretMutation,
@ -12,6 +14,8 @@ import TabToolbar from '../../entity/shared/components/styled/TabToolbar';
import { SecretBuilderModal } from './SecretBuilderModal';
import { SecretBuilderState } from './types';
import { StyledTable } from '../../entity/shared/components/styled/StyledTable';
import { SearchBar } from '../../search/SearchBar';
import { useEntityRegistry } from '../../useEntityRegistry';
const DeleteButtonContainer = styled.div`
display: flex;
@ -26,6 +30,13 @@ const SourcePaginationContainer = styled.div`
const DEFAULT_PAGE_SIZE = 25;
export const SecretsList = () => {
const entityRegistry = useEntityRegistry();
const location = useLocation();
const params = QueryString.parse(location.search, { arrayFormat: 'comma' });
const paramsQuery = (params?.query as string) || undefined;
const [query, setQuery] = useState<undefined | string>(undefined);
useEffect(() => setQuery(paramsQuery), [paramsQuery]);
const [page, setPage] = useState(1);
const pageSize = DEFAULT_PAGE_SIZE;
@ -42,6 +53,7 @@ export const SecretsList = () => {
input: {
start,
count: pageSize,
query,
},
},
fetchPolicy: 'no-cache',
@ -163,6 +175,22 @@ export const SecretsList = () => {
<PlusOutlined /> Create new secret
</Button>
</div>
<SearchBar
initialQuery={query || ''}
placeholderText="Search secrets..."
suggestions={[]}
style={{
maxWidth: 220,
padding: 0,
}}
inputStyle={{
height: 32,
fontSize: 12,
}}
onSearch={() => null}
onQueryChange={(q) => setQuery(q)}
entityRegistry={entityRegistry}
/>
</TabToolbar>
<StyledTable
columns={tableColumns}

View File

@ -1,5 +1,7 @@
import { CopyOutlined, DeleteOutlined, PlusOutlined, RedoOutlined } from '@ant-design/icons';
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import * as QueryString from 'query-string';
import { useLocation } from 'react-router';
import { Button, Empty, Image, message, Modal, Pagination, Tooltip, Typography } from 'antd';
import styled from 'styled-components';
import cronstrue from 'cronstrue';
@ -24,6 +26,8 @@ import {
import { DEFAULT_EXECUTOR_ID, SourceBuilderState } from './builder/types';
import { UpdateIngestionSourceInput } from '../../../types.generated';
import { capitalizeFirstLetter } from '../../shared/textUtil';
import { SearchBar } from '../../search/SearchBar';
import { useEntityRegistry } from '../../useEntityRegistry';
const SourceContainer = styled.div``;
@ -66,6 +70,13 @@ const removeExecutionsFromIngestionSource = (source) => {
};
export const IngestionSourceList = () => {
const entityRegistry = useEntityRegistry();
const location = useLocation();
const params = QueryString.parse(location.search, { arrayFormat: 'comma' });
const paramsQuery = (params?.query as string) || undefined;
const [query, setQuery] = useState<undefined | string>(undefined);
useEffect(() => setQuery(paramsQuery), [paramsQuery]);
const [page, setPage] = useState(1);
const pageSize = DEFAULT_PAGE_SIZE;
@ -83,6 +94,7 @@ export const IngestionSourceList = () => {
input: {
start,
count: pageSize,
query,
},
},
});
@ -404,6 +416,22 @@ export const IngestionSourceList = () => {
<RedoOutlined /> Refresh
</Button>
</div>
<SearchBar
initialQuery={query || ''}
placeholderText="Search sources..."
suggestions={[]}
style={{
maxWidth: 220,
padding: 0,
}}
inputStyle={{
height: 32,
fontSize: 12,
}}
onSearch={() => null}
onQueryChange={(q) => setQuery(q)}
entityRegistry={entityRegistry}
/>
</TabToolbar>
<StyledTable
columns={tableColumns}

View File

@ -1,7 +1,9 @@
import React, { useMemo, useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { Button, Empty, message, Modal, Pagination, Tag, Typography } from 'antd';
import styled from 'styled-components';
import * as QueryString from 'query-string';
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
import { useLocation } from 'react-router';
import { SearchablePage } from '../search/SearchablePage';
import PolicyBuilderModal from './PolicyBuilderModal';
@ -31,6 +33,7 @@ import { StyledTable } from '../entity/shared/components/styled/StyledTable';
import AvatarsGroup from './AvatarsGroup';
import { useEntityRegistry } from '../useEntityRegistry';
import { ANTD_GRAY } from '../entity/shared/constants';
import { SearchBar } from '../search/SearchBar';
const PoliciesContainer = styled.div`
padding-top: 20px;
@ -138,6 +141,12 @@ const toPolicyInput = (policy: Omit<Policy, 'urn'>): PolicyUpdateInput => {
// TODO: Cleanup the styling.
export const PoliciesPage = () => {
const entityRegistry = useEntityRegistry();
const location = useLocation();
const params = QueryString.parse(location.search, { arrayFormat: 'comma' });
const paramsQuery = (params?.query as string) || undefined;
const [query, setQuery] = useState<undefined | string>(undefined);
useEffect(() => setQuery(paramsQuery), [paramsQuery]);
const {
config: { policiesConfig },
} = useAppConfig();
@ -166,7 +175,13 @@ export const PoliciesPage = () => {
refetch: policiesRefetch,
} = useListPoliciesQuery({
fetchPolicy: 'no-cache',
variables: { input: { start, count: pageSize } },
variables: {
input: {
start,
count: pageSize,
query,
},
},
});
// Any time a policy is removed, edited, or created, refetch the list.
@ -402,7 +417,9 @@ export const PoliciesPage = () => {
return (
<SearchablePage>
{policiesLoading && <Message type="loading" content="Loading policies..." style={{ marginTop: '10%' }} />}
{policiesLoading && !policiesData && (
<Message type="loading" content="Loading policies..." style={{ marginTop: '10%' }} />
)}
{policiesError && message.error('Failed to load policies :(')}
{updateError && message.error('Failed to update the Policy :(')}
<PoliciesContainer>
@ -420,6 +437,22 @@ export const PoliciesPage = () => {
<PlusOutlined /> Create new policy
</Button>
</div>
<SearchBar
initialQuery={query || ''}
placeholderText="Search policies..."
suggestions={[]}
style={{
maxWidth: 220,
padding: 0,
}}
inputStyle={{
height: 32,
fontSize: 12,
}}
onSearch={() => null}
onQueryChange={(q) => setQuery(q)}
entityRegistry={entityRegistry}
/>
</TabToolbar>
<StyledTable
columns={tableColumns}

View File

@ -1,4 +1,4 @@
import React, { useMemo, useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { Input, AutoComplete, Image, Typography } from 'antd';
import { SearchOutlined } from '@ant-design/icons';
import styled from 'styled-components';
@ -185,6 +185,7 @@ export const SearchBar = ({
const history = useHistory();
const [searchQuery, setSearchQuery] = useState<string>();
const [selected, setSelected] = useState<string>();
useEffect(() => setSelected(initialQuery), [initialQuery]);
const searchEntityTypes = entityRegistry.getSearchEntityTypes();
const userUrn = useGetAuthenticatedUserUrn();
const { data } = useListRecommendationsQuery({

View File

@ -12,13 +12,16 @@ record DataHubPolicyInfo {
* Display name of the Policy
*/
@Searchable = {
"fieldType": "KEYWORD"
"fieldType": "TEXT_PARTIAL"
}
displayName: string
/**
* Description of the Policy
*/
@Searchable = {
"fieldType": "TEXT"
}
description: string
/**

View File

@ -39,10 +39,15 @@ public class PolicyFetcher {
public PolicyFetchResult fetchPolicies(int start, int count, Authentication authentication)
throws RemoteInvocationException, URISyntaxException {
return fetchPolicies(start, count, "", authentication);
}
public PolicyFetchResult fetchPolicies(int start, int count, String query, Authentication authentication)
throws RemoteInvocationException, URISyntaxException {
log.debug(String.format("Batch fetching policies. start: %s, count: %s ", start, count));
// First fetch all policy urns from start - start + count
SearchResult result =
_entityClient.search(POLICY_ENTITY_NAME, "*", null, POLICY_SORT_CRITERION, start, count, authentication);
_entityClient.search(POLICY_ENTITY_NAME, query, null, POLICY_SORT_CRITERION, start, count, authentication);
List<Urn> policyUrns = result.getEntities().stream().map(SearchEntity::getEntity).collect(Collectors.toList());
if (policyUrns.isEmpty()) {

View File

@ -75,7 +75,7 @@ public class DataHubAuthorizerTest {
policySearchResult.setEntities(new SearchEntityArray(ImmutableList.of(new SearchEntity().setEntity(activePolicyUrn),
new SearchEntity().setEntity(inactivePolicyUrn))));
when(_entityClient.search(eq("dataHubPolicy"), eq("*"), isNull(), any(), anyInt(), anyInt(), any())).thenReturn(
when(_entityClient.search(eq("dataHubPolicy"), eq(""), isNull(), any(), anyInt(), anyInt(), any())).thenReturn(
policySearchResult);
when(_entityClient.batchGetV2(eq(POLICY_ENTITY_NAME),
eq(ImmutableSet.of(activePolicyUrn, inactivePolicyUrn)), eq(null), any())).thenReturn(
@ -192,7 +192,7 @@ public class DataHubAuthorizerTest {
emptyResult.setNumEntities(0);
emptyResult.setEntities(new SearchEntityArray());
when(_entityClient.search(eq("dataHubPolicy"), eq("*"), isNull(), any(), anyInt(), anyInt(), any())).thenReturn(
when(_entityClient.search(eq("dataHubPolicy"), eq(""), isNull(), any(), anyInt(), anyInt(), any())).thenReturn(
emptyResult);
when(_entityClient.batchGetV2(eq(POLICY_ENTITY_NAME), eq(Collections.emptySet()), eq(null), any())).thenReturn(
Collections.emptyMap());