Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b5a2cf9
feat(search): Add canViewEntityPage permission flag field to search r…
Nov 3, 2025
40636a0
Merge branch 'master' into esimplicity-copilot/search-entity-page-vie…
yorubaphenom Nov 3, 2025
84796a0
linter happy
Nov 3, 2025
ee88f39
add canViewEntityPage value to mocks
Nov 3, 2025
fc59572
Merge branch 'master' into esimplicity-copilot/search-entity-page-vie…
yorubaphenom Nov 3, 2025
a3d3b79
Merge branch 'master' into esimplicity-copilot/search-entity-page-vie…
yorubaphenom Nov 3, 2025
2c60dbc
Merge branch 'master' into esimplicity-copilot/search-entity-page-vie…
yorubaphenom Nov 4, 2025
5d7acba
Merge branch 'master' into esimplicity-copilot/search-entity-page-vie…
yorubaphenom Nov 4, 2025
1417701
Merge branch 'master' into esimplicity-copilot/search-entity-page-vie…
yorubaphenom Nov 4, 2025
4a3a60c
boost test coverage for MapperUtils
Nov 4, 2025
4271055
linter green for MapperUtilsTest
Nov 4, 2025
74877af
address PR feeback to remove canViewEntityPage into extraProperties
Nov 10, 2025
8f85c9c
Merge branch 'master' into esimplicity-copilot/search-entity-page-vie…
yorubaphenom Nov 10, 2025
cb15a10
update Mocks.tsx and clean up MapperUtilsText
Nov 10, 2025
005d1dd
linter happy
Nov 10, 2025
8a70373
Merge branch 'master' into esimplicity-copilot/search-entity-page-vie…
yorubaphenom Nov 10, 2025
f73ba0c
Merge branch 'master' into esimplicity-copilot/search-entity-page-vie…
yorubaphenom Nov 11, 2025
0b4d995
add canViewEntityPage to EntityPrivileges
Nov 12, 2025
540d505
cleanup
Nov 12, 2025
d04ba64
Merge branch 'master' into esimplicity-copilot/search-entity-page-vie…
yorubaphenom Nov 12, 2025
3f10e02
test cases for EntityPrivilegesResolver
Nov 12, 2025
a45ca27
Merge branch 'master' into esimplicity-copilot/search-entity-page-vie…
yorubaphenom Nov 13, 2025
fedbe6e
Merge branch 'master' into esimplicity-copilot/search-entity-page-vie…
yorubaphenom Nov 20, 2025
79341e2
Merge branch 'master' into esimplicity-copilot/search-entity-page-vie…
yorubaphenom Nov 21, 2025
85d5e6f
Merge branch 'master' into esimplicity-copilot/search-entity-page-vie…
yorubaphenom Nov 22, 2025
eab03ba
Merge branch 'master' into esimplicity-copilot/search-entity-page-vie…
yorubaphenom Nov 25, 2025
b187bc4
Merge branch 'master' into esimplicity-copilot/search-entity-page-vie…
yorubaphenom Nov 25, 2025
0f60ac1
Merge branch 'master' into esimplicity-copilot/search-entity-page-vie…
yorubaphenom Nov 25, 2025
c6048c7
Merge branch 'master' into esimplicity-copilot/search-entity-page-vie…
yorubaphenom Nov 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static com.linkedin.datahub.graphql.util.SearchInsightsUtil.*;
import static com.linkedin.metadata.utils.SearchUtil.*;

import com.datahub.authorization.EntitySpec;
import com.linkedin.common.AuditStamp;
import com.linkedin.common.UrnArray;
import com.linkedin.common.urn.Urn;
Expand Down Expand Up @@ -37,11 +38,36 @@ private MapperUtils() {}

public static SearchResult mapResult(
@Nullable final QueryContext context, SearchEntity searchEntity) {
return new SearchResult(
UrnToEntityMapper.map(context, searchEntity.getEntity()),
getInsightsFromFeatures(searchEntity.getFeatures()),
getMatchedFieldEntry(context, searchEntity.getMatchedFields()),
getExtraProperties(searchEntity.getExtraFields()));
SearchResult result =
new SearchResult(
UrnToEntityMapper.map(context, searchEntity.getEntity()),
getInsightsFromFeatures(searchEntity.getFeatures()),
getMatchedFieldEntry(context, searchEntity.getMatchedFields()),
getExtraProperties(searchEntity.getExtraFields()),
null); // Initialize canViewEntityPage as null

// Check if the user can view the entity page
if (context != null) {
try {
Urn entityUrn = searchEntity.getEntity();
EntitySpec entitySpec = new EntitySpec(entityUrn.getEntityType(), entityUrn.toString());
// Use the authorize method from OperationContext
boolean canView =
context.getOperationContext().authorize("VIEW_ENTITY_PAGE", entitySpec).getType()
== com.datahub.authorization.AuthorizationResult.Type.ALLOW;
result.setCanViewEntityPage(canView);
} catch (Exception e) {
log.warn(
"Failed to check VIEW_ENTITY_PAGE permission for entity {}",
searchEntity.getEntity(),
e);
result.setCanViewEntityPage(true); // Default to true if permission check fails
}
} else {
result.setCanViewEntityPage(true); // Default to true if no context
}

return result;
}

private static List<ExtraProperty> getExtraProperties(@Nullable StringMap extraFields) {
Expand Down
5 changes: 5 additions & 0 deletions datahub-graphql-core/src/main/resources/search.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,11 @@ type SearchResult {
Additional properties about the search result. Used for rendering in the UI
"""
extraProperties: [ExtraProperty!]

"""
Whether the current user can view the entity page for this result
"""
canViewEntityPage: Boolean
}

"""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package com.linkedin.datahub.graphql.types.mappers;

import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertThrows;

Expand All @@ -8,10 +12,14 @@
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.TestUtils;
import com.linkedin.datahub.graphql.generated.MatchedField;
import com.linkedin.datahub.graphql.generated.SearchResult;
import com.linkedin.metadata.entity.validation.ValidationApiUtils;
import com.linkedin.metadata.models.registry.ConfigEntityRegistry;
import com.linkedin.metadata.models.registry.EntityRegistry;
import com.linkedin.metadata.search.MatchedFieldArray;
import com.linkedin.metadata.search.SearchEntity;
import com.linkedin.metadata.snapshot.Snapshot;
import io.datahubproject.metadata.context.OperationContext;
import java.net.URISyntaxException;
import java.util.List;
import org.testng.annotations.BeforeTest;
Expand Down Expand Up @@ -58,6 +66,49 @@ public void testMatchedFieldValidation() throws URISyntaxException {
"With urn should be 1");
}

@Test
public void testMapResultDefaultsCanViewEntityPageWithoutContext() throws URISyntaxException {
SearchEntity searchEntity = buildSearchEntity();

SearchResult result = MapperUtils.mapResult(null, searchEntity);

assertEquals(result.getCanViewEntityPage(), Boolean.TRUE);
}

@Test
public void testMapResultSetsCanViewEntityPageWhenAuthorized() throws URISyntaxException {
QueryContext context = TestUtils.getMockAllowContext();
SearchEntity searchEntity = buildSearchEntity();

SearchResult result = MapperUtils.mapResult(context, searchEntity);

assertEquals(result.getCanViewEntityPage(), Boolean.TRUE);
}

@Test
public void testMapResultSetsCanViewEntityPageWhenUnauthorized() throws URISyntaxException {
QueryContext context = TestUtils.getMockDenyContext();
SearchEntity searchEntity = buildSearchEntity();

SearchResult result = MapperUtils.mapResult(context, searchEntity);

assertEquals(result.getCanViewEntityPage(), Boolean.FALSE);
}

@Test
public void testMapResultDefaultsCanViewEntityPageOnFailure() throws URISyntaxException {
QueryContext context = mock(QueryContext.class);
OperationContext operationContext = mock(OperationContext.class);
when(context.getOperationContext()).thenReturn(operationContext);
when(operationContext.authorize(eq("VIEW_ENTITY_PAGE"), any()))
.thenThrow(new RuntimeException("boom"));
SearchEntity searchEntity = buildSearchEntity();

SearchResult result = MapperUtils.mapResult(context, searchEntity);

assertEquals(result.getCanViewEntityPage(), Boolean.TRUE);
}

private static com.linkedin.metadata.search.MatchedField buildSearchMatchField(
String highlightValue) {
com.linkedin.metadata.search.MatchedField field =
Expand All @@ -66,4 +117,12 @@ private static com.linkedin.metadata.search.MatchedField buildSearchMatchField(
field.setValue(highlightValue);
return field;
}

private static SearchEntity buildSearchEntity() throws URISyntaxException {
SearchEntity searchEntity = new SearchEntity();
searchEntity.setEntity(
Urn.createFromString("urn:li:dataset:(urn:li:dataPlatform:s3,testDataset,PROD)"));
searchEntity.setMatchedFields(new MatchedFieldArray());
return searchEntity;
}
}
26 changes: 25 additions & 1 deletion datahub-web-react/src/Mocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2332,6 +2332,7 @@ export const mocks = [
entity: {
...dataset1,
},
canViewEntityPage: true,
matchedFields: [
{
name: 'fieldName',
Expand All @@ -2344,12 +2345,14 @@ export const mocks = [
entity: {
...dataset2,
},
canViewEntityPage: true,
},
{
entity: {
__typename: 'Dataset',
...dataset3,
},
canViewEntityPage: true,
},
],
facets: [
Expand Down Expand Up @@ -2425,6 +2428,7 @@ export const mocks = [
__typename: 'Dataset',
...dataset3,
},
canViewEntityPage: true,
matchedFields: [],
insights: [],
},
Expand Down Expand Up @@ -2497,6 +2501,7 @@ export const mocks = [
__typename: 'GLOSSARY_TERM',
...glossaryTerm1,
},
canViewEntityPage: true,
matchedFields: [],
insights: [],
},
Expand Down Expand Up @@ -2605,6 +2610,7 @@ export const mocks = [
__typename: 'Dataset',
...dataset3,
},
canViewEntityPage: true,
matchedFields: [],
insights: [],
},
Expand Down Expand Up @@ -2779,6 +2785,7 @@ export const mocks = [
__typename: 'Dataset',
...dataset3,
},
canViewEntityPage: true,
matchedFields: [],
insights: [],
},
Expand Down Expand Up @@ -2848,6 +2855,7 @@ export const mocks = [
__typename: 'Dataset',
...dataset3,
},
canViewEntityPage: true,
matchedFields: [],
insights: [],
},
Expand All @@ -2856,6 +2864,7 @@ export const mocks = [
__typename: 'Dataset',
...dataset4,
},
canViewEntityPage: true,
matchedFields: [],
insights: [],
},
Expand Down Expand Up @@ -2925,6 +2934,7 @@ export const mocks = [
__typename: 'DataFlow',
...dataFlow1,
},
canViewEntityPage: true,
matchedFields: [],
insights: [],
},
Expand Down Expand Up @@ -3071,6 +3081,7 @@ export const mocks = [
__typename: 'DataJob',
...dataJob1,
},
canViewEntityPage: true,
matchedFields: [],
insights: [],
},
Expand Down Expand Up @@ -3167,6 +3178,7 @@ export const mocks = [
__typename: 'Dataset',
...dataset3,
},
canViewEntityPage: true,
matchedFields: [],
insights: [],
},
Expand Down Expand Up @@ -3287,6 +3299,7 @@ export const mocks = [
__typename: 'Dataset',
...dataset3,
},
canViewEntityPage: true,
matchedFields: [],
insights: [],
},
Expand Down Expand Up @@ -3337,6 +3350,7 @@ export const mocks = [
__typename: 'DataJob',
...dataJob1,
},
canViewEntityPage: true,
matchedFields: [],
insights: [],
},
Expand Down Expand Up @@ -3414,6 +3428,7 @@ export const mocks = [
__typename: 'Dataset',
...dataset3,
},
canViewEntityPage: true,
matchedFields: [],
insights: [],
},
Expand Down Expand Up @@ -3496,6 +3511,7 @@ export const mocks = [
__typename: 'Dataset',
...dataset3,
},
canViewEntityPage: true,
matchedFields: [],
insights: [],
},
Expand All @@ -3504,6 +3520,7 @@ export const mocks = [
__typename: 'Dataset',
...dataset4,
},
canViewEntityPage: true,
matchedFields: [],
insights: [],
},
Expand Down Expand Up @@ -3593,6 +3610,7 @@ export const mocks = [
__typename: 'Dataset',
...dataset3,
},
canViewEntityPage: true,
matchedFields: [],
insights: [],
},
Expand Down Expand Up @@ -3686,6 +3704,7 @@ export const mocks = [
__typename: 'Dataset',
...dataset3,
},
canViewEntityPage: true,
matchedFields: [],
insights: [],
},
Expand Down Expand Up @@ -3957,6 +3976,7 @@ export const mocks = [
__typename: 'Dataset',
...dataset3,
},
canViewEntityPage: true,
matchedFields: [],
insights: [],
},
Expand All @@ -3965,6 +3985,7 @@ export const mocks = [
__typename: 'Dataset',
...dataset4,
},
canViewEntityPage: true,
matchedFields: [],
insights: [],
},
Expand Down Expand Up @@ -4360,6 +4381,7 @@ export const mockSearchResult: SearchResult = {
__typename: 'Dataset',
...dataset3,
},
canViewEntityPage: true,
matchedFields: [],
insights: [],
extraProperties: [
Expand All @@ -4385,7 +4407,9 @@ export const mockFineGrainedLineages1: GenericEntityProperties = {
siblingsSearch: {
count: 1,
total: 1,
searchResults: [{ entity: { type: EntityType.Dataset, urn: 'test_urn' }, matchedFields: [] }],
searchResults: [
{ entity: { type: EntityType.Dataset, urn: 'test_urn' }, matchedFields: [], canViewEntityPage: true },
],
},
fineGrainedLineages: [
{
Expand Down
1 change: 1 addition & 0 deletions datahub-web-react/src/app/entity/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,4 +208,5 @@ export type RequiredAndNotNull<T> = {
export type EntityAndType = {
urn: string;
type: EntityType;
canViewEntityPage?: boolean | null;
};
1 change: 1 addition & 0 deletions datahub-web-react/src/graphql/search.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1299,6 +1299,7 @@ fragment searchResults on SearchResults {
text
icon
}
canViewEntityPage
}
facets {
...facetFields
Expand Down
Loading