diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/analytics/resolver/GetChartsResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/analytics/resolver/GetChartsResolver.java index 394a305e3f4d0a..767c9b4d4e71bc 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/analytics/resolver/GetChartsResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/analytics/resolver/GetChartsResolver.java @@ -1,5 +1,8 @@ package com.linkedin.datahub.graphql.analytics.resolver; +import static com.linkedin.metadata.Constants.CORP_USER_ENTITY_NAME; +import static com.linkedin.metadata.Constants.CORP_USER_STATUS_LAST_MODIFIED_FIELD_NAME; + import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -9,17 +12,31 @@ import com.linkedin.datahub.graphql.generated.AnalyticsChart; import com.linkedin.datahub.graphql.generated.AnalyticsChartGroup; import com.linkedin.datahub.graphql.generated.BarChart; +import com.linkedin.datahub.graphql.generated.Cell; import com.linkedin.datahub.graphql.generated.DateInterval; import com.linkedin.datahub.graphql.generated.DateRange; +import com.linkedin.datahub.graphql.generated.EntityProfileParams; import com.linkedin.datahub.graphql.generated.EntityType; +import com.linkedin.datahub.graphql.generated.LinkParams; import com.linkedin.datahub.graphql.generated.NamedBar; import com.linkedin.datahub.graphql.generated.NamedLine; import com.linkedin.datahub.graphql.generated.Row; import com.linkedin.datahub.graphql.generated.TableChart; import com.linkedin.datahub.graphql.generated.TimeSeriesChart; +import com.linkedin.datahub.graphql.types.common.mappers.UrnToEntityMapper; import com.linkedin.datahub.graphql.util.DateUtil; import com.linkedin.entity.client.EntityClient; import com.linkedin.metadata.Constants; +import com.linkedin.metadata.query.filter.Condition; +import com.linkedin.metadata.query.filter.ConjunctiveCriterion; +import com.linkedin.metadata.query.filter.ConjunctiveCriterionArray; +import com.linkedin.metadata.query.filter.Criterion; +import com.linkedin.metadata.query.filter.CriterionArray; +import com.linkedin.metadata.query.filter.Filter; +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 io.datahubproject.metadata.context.OperationContext; @@ -28,6 +45,7 @@ import java.util.List; import java.util.Optional; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.joda.time.DateTime; @@ -41,14 +59,13 @@ public final class GetChartsResolver implements DataFetcher get(DataFetchingEnvironment environment) throws Exception { + public List get(DataFetchingEnvironment environment) throws Exception { final QueryContext context = environment.getContext(); - try { return ImmutableList.of( AnalyticsChartGroup.builder() .setGroupId("DataHubUsageAnalytics") - .setTitle("DataHub Usage Analytics") + .setTitle("Usage Analytics") .setCharts(getProductAnalyticsCharts(context.getOperationContext())) .build(), AnalyticsChartGroup.builder() @@ -87,8 +104,106 @@ private TimeSeriesChart getActiveUsersTimeSeriesChart( .build(); } + @Nullable + private AnalyticsChart getTopUsersChart(OperationContext opContext) { + try { + final DateUtil dateUtil = new DateUtil(); + final DateRange trailingMonthDateRange = dateUtil.getTrailingMonthDateRange(); + final List columns = ImmutableList.of("Name", "Title", "Email"); + + final String topUsersTitle = "Top Users"; + final List topUserRows = + _analyticsService.getTopNTableChart( + _analyticsService.getUsageIndexName(), + Optional.of(trailingMonthDateRange), + "actorUrn.keyword", + Collections.emptyMap(), + ImmutableMap.of( + "actorUrn.keyword", + ImmutableList.of("urn:li:corpuser:admin", "urn:li:corpuser:datahub")), + Optional.empty(), + 30, + AnalyticsUtil::buildCellWithEntityLandingPage); + AnalyticsUtil.convertToUserInfoRows(opContext, _entityClient, topUserRows); + return TableChart.builder() + .setTitle(topUsersTitle) + .setColumns(columns) + .setRows(topUserRows) + .build(); + } catch (Exception e) { + log.error("Failed to retrieve top users chart!", e); + return null; + } + } + + private SearchResult searchForNewUsers(@Nonnull final OperationContext opContext) + throws Exception { + // Search for new users in the past month. + final DateUtil dateUtil = new DateUtil(); + final DateRange trailingMonthDateRange = dateUtil.getTrailingMonthDateRange(); + return _entityClient.search( + opContext, + CORP_USER_ENTITY_NAME, + "*", + new Filter() + .setOr( + new ConjunctiveCriterionArray( + ImmutableList.of( + new ConjunctiveCriterion() + .setAnd( + new CriterionArray( + ImmutableList.of( + new Criterion() + .setField(CORP_USER_STATUS_LAST_MODIFIED_FIELD_NAME) + .setCondition(Condition.GREATER_THAN) + .setValue( + String.valueOf( + trailingMonthDateRange.getStart())))))))), + new SortCriterion() + .setField(CORP_USER_STATUS_LAST_MODIFIED_FIELD_NAME) + .setOrder(SortOrder.DESCENDING), + 0, + 100); + } + + @Nonnull + private Row buildNewUsersRow(@Nonnull final SearchEntity entity) { + final Row row = new Row(); + row.setValues(ImmutableList.of(entity.getEntity().toString())); + final Cell cell = new Cell(); + cell.setValue(entity.getEntity().toString()); + cell.setEntity(UrnToEntityMapper.map(null, entity.getEntity())); + cell.setLinkParams( + new LinkParams( + null, new EntityProfileParams(entity.getEntity().toString(), EntityType.CORP_USER))); + row.setCells(ImmutableList.of(cell)); + return row; + } + + @Nullable + private AnalyticsChart getNewUsersChart(OperationContext opContext) { + try { + final List columns = ImmutableList.of("Name", "Title", "Email"); + final String newUsersTitle = "New Users"; + final SearchResult result = searchForNewUsers(opContext); + final List newUserRows = new ArrayList<>(); + for (SearchEntity entity : result.getEntities()) { + newUserRows.add(buildNewUsersRow(entity)); + } + AnalyticsUtil.convertToUserInfoRows(opContext, _entityClient, newUserRows); + return TableChart.builder() + .setTitle(newUsersTitle) + .setColumns(columns) + .setRows(newUserRows) + .build(); + } catch (Exception e) { + log.error("Failed to retrieve new users chart!", e); + return null; + } + } + /** TODO: Config Driven Charts Instead of Hardcoded. */ - private List getProductAnalyticsCharts(@Nonnull OperationContext opContext) + private List getProductAnalyticsCharts(OperationContext opContext) throws Exception { final List charts = new ArrayList<>(); DateUtil dateUtil = new DateUtil(); @@ -97,12 +212,15 @@ private List getProductAnalyticsCharts(@Nonnull OperationContext final DateTime startOfNextMonth = dateUtil.getStartOfNextMonth(); final DateRange trailingWeekDateRange = dateUtil.getTrailingWeekDateRange(); + // WAU charts.add( getActiveUsersTimeSeriesChart( startOfNextWeek.minusWeeks(10), startOfNextWeek.minusMillis(1), "Weekly Active Users", DateInterval.WEEK)); + + // MAU charts.add( getActiveUsersTimeSeriesChart( startOfNextMonth.minusMonths(12), @@ -110,7 +228,19 @@ private List getProductAnalyticsCharts(@Nonnull OperationContext "Monthly Active Users", DateInterval.MONTH)); - String searchesTitle = "Searches Last Week"; + // New users chart - past month + final AnalyticsChart newUsersChart = getNewUsersChart(opContext); + if (newUsersChart != null) { + charts.add(newUsersChart); + } + + // Top users chart - past month + final AnalyticsChart topUsersChart = getTopUsersChart(opContext); + if (topUsersChart != null) { + charts.add(topUsersChart); + } + + String searchesTitle = "Number of Searches"; DateInterval dailyInterval = DateInterval.DAY; String searchEventType = "SearchEvent"; @@ -131,7 +261,7 @@ private List getProductAnalyticsCharts(@Nonnull OperationContext .setLines(searchesTimeseries) .build()); - final String topSearchTitle = "Top Search Queries"; + final String topSearchTitle = "Top Searches (Past Week)"; final List columns = ImmutableList.of("Query", "Count"); final List topSearchQueries = @@ -151,34 +281,8 @@ private List getProductAnalyticsCharts(@Nonnull OperationContext .setRows(topSearchQueries) .build()); - final String sectionViewsTitle = "Section Views across Entity Types"; - final List sectionViewsPerEntityType = - _analyticsService.getBarChart( - _analyticsService.getUsageIndexName(), - Optional.of(trailingWeekDateRange), - ImmutableList.of("entityType.keyword", "section.keyword"), - ImmutableMap.of("type", ImmutableList.of("EntitySectionViewEvent")), - Collections.emptyMap(), - Optional.empty(), - true); - charts.add( - BarChart.builder().setTitle(sectionViewsTitle).setBars(sectionViewsPerEntityType).build()); - - final String actionsByTypeTitle = "Actions by Entity Type"; - final List eventsByEventType = - _analyticsService.getBarChart( - _analyticsService.getUsageIndexName(), - Optional.of(trailingWeekDateRange), - ImmutableList.of("entityType.keyword", "actionType.keyword"), - ImmutableMap.of("type", ImmutableList.of("EntityActionEvent")), - Collections.emptyMap(), - Optional.empty(), - true); - charts.add(BarChart.builder().setTitle(actionsByTypeTitle).setBars(eventsByEventType).build()); - - final String topViewedTitle = "Top Viewed Dataset"; - final List columns5 = ImmutableList.of("Dataset", "#Views"); - + final String topViewedDatasetsTitle = "Top Viewed Datasets (Past Week)"; + final List columns5 = ImmutableList.of("Name", "View Count"); final List topViewedDatasets = _analyticsService.getTopNTableChart( _analyticsService.getUsageIndexName(), @@ -198,19 +302,75 @@ private List getProductAnalyticsCharts(@Nonnull OperationContext _entityClient, topViewedDatasets, Constants.DATASET_ENTITY_NAME, - ImmutableSet.of(Constants.DATASET_KEY_ASPECT_NAME), + ImmutableSet.of( + Constants.DATASET_KEY_ASPECT_NAME, Constants.DATASET_PROPERTIES_ASPECT_NAME), AnalyticsUtil::getDatasetName); charts.add( TableChart.builder() - .setTitle(topViewedTitle) + .setTitle(topViewedDatasetsTitle) .setColumns(columns5) .setRows(topViewedDatasets) .build()); + final String topViewedDashboardsTitle = "Top Viewed Dashboards (Past Week)"; + final List columns6 = ImmutableList.of("Name", "View Count"); + final List topViewedDashboards = + _analyticsService.getTopNTableChart( + _analyticsService.getUsageIndexName(), + Optional.of(trailingWeekDateRange), + "entityUrn.keyword", + ImmutableMap.of( + "type", + ImmutableList.of("EntityViewEvent"), + "entityType.keyword", + ImmutableList.of(EntityType.DASHBOARD.name())), + Collections.emptyMap(), + Optional.empty(), + 10, + AnalyticsUtil::buildCellWithEntityLandingPage); + AnalyticsUtil.hydrateDisplayNameForTable( + opContext, + _entityClient, + topViewedDashboards, + Constants.DASHBOARD_ENTITY_NAME, + ImmutableSet.of(Constants.DASHBOARD_INFO_ASPECT_NAME), + AnalyticsUtil::getDashboardName); + charts.add( + TableChart.builder() + .setTitle(topViewedDashboardsTitle) + .setColumns(columns6) + .setRows(topViewedDashboards) + .build()); + + final String sectionViewsTitle = "Tab Views By Entity Type (Past Week)"; + final List sectionViewsPerEntityType = + _analyticsService.getBarChart( + _analyticsService.getUsageIndexName(), + Optional.of(trailingWeekDateRange), + ImmutableList.of("entityType.keyword", "section.keyword"), + ImmutableMap.of("type", ImmutableList.of("EntitySectionViewEvent")), + Collections.emptyMap(), + Optional.empty(), + true); + charts.add( + BarChart.builder().setTitle(sectionViewsTitle).setBars(sectionViewsPerEntityType).build()); + + final String actionsByTypeTitle = "Actions By Entity Type (Past Week)"; + final List eventsByEventType = + _analyticsService.getBarChart( + _analyticsService.getUsageIndexName(), + Optional.of(trailingWeekDateRange), + ImmutableList.of("entityType.keyword", "actionType.keyword"), + ImmutableMap.of("type", ImmutableList.of("EntityActionEvent")), + Collections.emptyMap(), + Optional.empty(), + true); + charts.add(BarChart.builder().setTitle(actionsByTypeTitle).setBars(eventsByEventType).build()); + return charts; } - private List getGlobalMetadataAnalyticsCharts(@Nonnull OperationContext opContext) + private List getGlobalMetadataAnalyticsCharts(OperationContext opContext) throws Exception { final List charts = new ArrayList<>(); // Chart 1: Entities per domain @@ -239,7 +399,7 @@ private List getGlobalMetadataAnalyticsCharts(@Nonnull Operation AnalyticsUtil::getPlatformName); if (!entitiesPerDomain.isEmpty()) { charts.add( - BarChart.builder().setTitle("Entities per Domain").setBars(entitiesPerDomain).build()); + BarChart.builder().setTitle("Entities By Domain").setBars(entitiesPerDomain).build()); } // Chart 2: Entities per platform @@ -261,10 +421,7 @@ private List getGlobalMetadataAnalyticsCharts(@Nonnull Operation AnalyticsUtil::getPlatformName); if (!entitiesPerPlatform.isEmpty()) { charts.add( - BarChart.builder() - .setTitle("Entities per Platform") - .setBars(entitiesPerPlatform) - .build()); + BarChart.builder().setTitle("Assets By Platform").setBars(entitiesPerPlatform).build()); } // Chart 3: Entities per term @@ -286,7 +443,8 @@ private List getGlobalMetadataAnalyticsCharts(@Nonnull Operation Constants.GLOSSARY_TERM_KEY_ASPECT_NAME, Constants.GLOSSARY_TERM_INFO_ASPECT_NAME), AnalyticsUtil::getTermName); if (!entitiesPerTerm.isEmpty()) { - charts.add(BarChart.builder().setTitle("Entities per Term").setBars(entitiesPerTerm).build()); + charts.add( + BarChart.builder().setTitle("Entities With Term").setBars(entitiesPerTerm).build()); } // Chart 4: Entities per fabric type @@ -301,7 +459,7 @@ private List getGlobalMetadataAnalyticsCharts(@Nonnull Operation false); if (entitiesPerEnv.size() > 1) { charts.add( - BarChart.builder().setTitle("Entities per Environment").setBars(entitiesPerEnv).build()); + BarChart.builder().setTitle("Entities By Environment").setBars(entitiesPerEnv).build()); } return charts; diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/analytics/resolver/GetMetadataAnalyticsResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/analytics/resolver/GetMetadataAnalyticsResolver.java index b21bd270a9dc15..01f2e6c8462e39 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/analytics/resolver/GetMetadataAnalyticsResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/analytics/resolver/GetMetadataAnalyticsResolver.java @@ -96,7 +96,7 @@ private List getCharts(MetadataAnalyticsInput input, OperationCo Constants.DOMAIN_ENTITY_NAME, ImmutableSet.of(Constants.DOMAIN_PROPERTIES_ASPECT_NAME), AnalyticsUtil::getDomainName); - charts.add(BarChart.builder().setTitle("Entities by Domain").setBars(domainChart).build()); + charts.add(BarChart.builder().setTitle("Data Assets by Domain").setBars(domainChart).build()); } Optional platformAggregation = @@ -114,7 +114,7 @@ private List getCharts(MetadataAnalyticsInput input, OperationCo ImmutableSet.of(Constants.DATA_PLATFORM_INFO_ASPECT_NAME), AnalyticsUtil::getPlatformName); charts.add( - BarChart.builder().setTitle("Entities by Platform").setBars(platformChart).build()); + BarChart.builder().setTitle("Data Assets by Platform").setBars(platformChart).build()); } Optional termAggregation = @@ -132,7 +132,7 @@ private List getCharts(MetadataAnalyticsInput input, OperationCo ImmutableSet.of( Constants.GLOSSARY_TERM_KEY_ASPECT_NAME, Constants.GLOSSARY_TERM_INFO_ASPECT_NAME), AnalyticsUtil::getTermName); - charts.add(BarChart.builder().setTitle("Entities by Term").setBars(termChart).build()); + charts.add(BarChart.builder().setTitle("Data Assets by Term").setBars(termChart).build()); } Optional envAggregation = @@ -144,7 +144,7 @@ private List getCharts(MetadataAnalyticsInput input, OperationCo List termChart = buildBarChart(envAggregation.get()); if (termChart.size() > 1) { charts.add( - BarChart.builder().setTitle("Entities by Environment").setBars(termChart).build()); + BarChart.builder().setTitle("Data Assets by Environment").setBars(termChart).build()); } } @@ -162,7 +162,7 @@ private List buildBarChart(AggregationMetadata aggregation) { .setSegments( ImmutableList.of( BarSegment.builder() - .setLabel("#Entities") + .setLabel("Count") .setValue(entry.getValue().intValue()) .build())) .build()) diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/analytics/service/AnalyticsUtil.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/analytics/service/AnalyticsUtil.java index ab60a6d8116b17..a17745948eb823 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/analytics/service/AnalyticsUtil.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/analytics/service/AnalyticsUtil.java @@ -1,6 +1,11 @@ package com.linkedin.datahub.graphql.analytics.service; +import static com.linkedin.metadata.Constants.CORP_USER_INFO_ASPECT_NAME; + +import com.google.common.collect.ImmutableSet; import com.linkedin.common.urn.Urn; +import com.linkedin.common.urn.UrnUtils; +import com.linkedin.dashboard.DashboardInfo; import com.linkedin.datahub.graphql.generated.BarSegment; import com.linkedin.datahub.graphql.generated.Cell; import com.linkedin.datahub.graphql.generated.Entity; @@ -11,16 +16,18 @@ import com.linkedin.datahub.graphql.generated.SearchParams; import com.linkedin.datahub.graphql.types.common.mappers.UrnToEntityMapper; import com.linkedin.dataplatform.DataPlatformInfo; +import com.linkedin.dataset.DatasetProperties; import com.linkedin.domain.DomainProperties; import com.linkedin.entity.EntityResponse; import com.linkedin.entity.EnvelopedAspect; import com.linkedin.entity.client.EntityClient; import com.linkedin.glossary.GlossaryTermInfo; +import com.linkedin.identity.CorpUserInfo; import com.linkedin.metadata.Constants; -import com.linkedin.metadata.key.DatasetKey; import com.linkedin.metadata.key.GlossaryTermKey; import io.datahubproject.metadata.context.OperationContext; import java.net.URISyntaxException; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; @@ -29,6 +36,7 @@ import java.util.function.Function; import java.util.stream.Collectors; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.tuple.Pair; @@ -151,6 +159,60 @@ public static void hydrateDisplayNameForTable( })); } + public static void convertToUserInfoRows( + OperationContext opContext, EntityClient entityClient, List rows) throws Exception { + final Set userUrns = + rows.stream() + .filter(row -> !row.getCells().isEmpty()) + .map(row -> UrnUtils.getUrn(row.getCells().get(0).getValue())) + .collect(Collectors.toSet()); + final Map gmsResponseByUser = + entityClient.batchGetV2( + opContext, + CORP_USER_INFO_ASPECT_NAME, + userUrns, + ImmutableSet.of(CORP_USER_INFO_ASPECT_NAME)); + final Map urnToCorpUserInfo = + gmsResponseByUser.entrySet().stream() + .filter( + entry -> + entry.getValue() != null + && entry.getValue().getAspects().containsKey(CORP_USER_INFO_ASPECT_NAME)) + .collect( + Collectors.toMap( + Map.Entry::getKey, + entry -> + new CorpUserInfo( + entry + .getValue() + .getAspects() + .get(CORP_USER_INFO_ASPECT_NAME) + .getValue() + .data()))); + // Populate a row with the user link, title, and email. + rows.forEach( + row -> { + Urn urn = UrnUtils.getUrn(row.getCells().get(0).getValue()); + EntityResponse response = gmsResponseByUser.get(urn); + String maybeDisplayName = response != null ? getUserName(response).orElse(null) : null; + String maybeEmail = + urnToCorpUserInfo.containsKey(urn) ? urnToCorpUserInfo.get(urn).getEmail() : null; + String maybeTitle = + urnToCorpUserInfo.containsKey(urn) ? urnToCorpUserInfo.get(urn).getTitle() : null; + if (maybeDisplayName != null) { + row.getCells().get(0).setValue(maybeDisplayName); + } + final List newCells = new ArrayList<>(); + // First add the user cell + newCells.add(row.getCells().get(0)); + // Then, add the title row. + newCells.add(new Cell(maybeTitle != null ? maybeTitle : "None", null, null)); + // Finally, add the email row. + newCells.add(new Cell(maybeEmail != null ? maybeEmail : "None", null, null)); + row.setCells(newCells); + }); + } + public static Map getUrnToDisplayName( @Nonnull OperationContext opContext, EntityClient entityClient, @@ -194,24 +256,62 @@ public static Optional getPlatformName(EntityResponse entityResponse) { EnvelopedAspect envelopedDataPlatformInfo = entityResponse.getAspects().get(Constants.DATA_PLATFORM_INFO_ASPECT_NAME); if (envelopedDataPlatformInfo == null) { - return Optional.empty(); + return Optional.of(entityResponse.getUrn().getId()); } DataPlatformInfo dataPlatformInfo = new DataPlatformInfo(envelopedDataPlatformInfo.getValue().data()); - return Optional.of( + final String infoDisplayName = dataPlatformInfo.getDisplayName() == null ? dataPlatformInfo.getName() - : dataPlatformInfo.getDisplayName()); + : dataPlatformInfo.getDisplayName(); + return Optional.of(infoDisplayName != null ? infoDisplayName : entityResponse.getUrn().getId()); } public static Optional getDatasetName(EntityResponse entityResponse) { - EnvelopedAspect envelopedDatasetKey = - entityResponse.getAspects().get(Constants.DATASET_KEY_ASPECT_NAME); - if (envelopedDatasetKey == null) { + EnvelopedAspect envelopedDatasetProperties = + entityResponse.getAspects().get(Constants.DATASET_PROPERTIES_ASPECT_NAME); + if (envelopedDatasetProperties == null) { return Optional.empty(); } - DatasetKey datasetKey = new DatasetKey(envelopedDatasetKey.getValue().data()); - return Optional.of(datasetKey.getName()); + DatasetProperties datasetProperties = + new DatasetProperties(envelopedDatasetProperties.getValue().data()); + return Optional.of( + datasetProperties.hasName() + ? datasetProperties.getName() + : entityResponse.getUrn().getEntityKey().get(1)); + } + + public static Optional getDashboardName(EntityResponse entityResponse) { + EnvelopedAspect envelopedDashboardName = + entityResponse.getAspects().get(Constants.DASHBOARD_INFO_ASPECT_NAME); + if (envelopedDashboardName == null) { + return Optional.empty(); + } + DashboardInfo dashboardInfo = new DashboardInfo(envelopedDashboardName.getValue().data()); + return Optional.of(dashboardInfo.getTitle()); + } + + public static Optional getUserName(EntityResponse entityResponse) { + EnvelopedAspect envelopedCorpUserInfo = + entityResponse.getAspects().get(CORP_USER_INFO_ASPECT_NAME); + if (envelopedCorpUserInfo == null) { + return Optional.of(entityResponse.getUrn().getId()); + } + CorpUserInfo corpUserInfo = new CorpUserInfo(envelopedCorpUserInfo.getValue().data()); + final String userInfoName = + corpUserInfo.hasDisplayName() + ? corpUserInfo.getDisplayName() + : getUserFullName(corpUserInfo.getFirstName(), corpUserInfo.getLastName()); + return Optional.of(userInfoName != null ? userInfoName : entityResponse.getUrn().getId()); + } + + @Nullable + private static String getUserFullName( + @Nullable final String firstName, @Nullable final String lastName) { + if (firstName != null && lastName != null) { + return firstName + " " + lastName; + } + return null; } public static Optional getTermName(EntityResponse entityResponse) { diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/util/DateUtil.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/util/DateUtil.java index 677ad8afbaca31..600db4ac04fc58 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/util/DateUtil.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/util/DateUtil.java @@ -35,4 +35,11 @@ public DateRange getTrailingWeekDateRange() { return new DateRange( String.valueOf(aWeekAgoStart.getMillis()), String.valueOf(todayEnd.getMillis())); } + + public DateRange getTrailingMonthDateRange() { + final DateTime todayEnd = getTomorrowStart().minusMillis(1); + final DateTime aMonthAgoStart = todayEnd.minusMonths(1).plusMillis(1); + return new DateRange( + String.valueOf(aMonthAgoStart.getMillis()), String.valueOf(todayEnd.getMillis())); + } } diff --git a/li-utils/src/main/java/com/linkedin/metadata/Constants.java b/li-utils/src/main/java/com/linkedin/metadata/Constants.java index 34fe5493a24be3..c200a4bc30d19c 100644 --- a/li-utils/src/main/java/com/linkedin/metadata/Constants.java +++ b/li-utils/src/main/java/com/linkedin/metadata/Constants.java @@ -125,6 +125,7 @@ public class Constants { public static final String ROLE_MEMBERSHIP_ASPECT_NAME = "roleMembership"; public static final String CORP_USER_SETTINGS_ASPECT_NAME = "corpUserSettings"; + public static final String CORP_USER_STATUS_LAST_MODIFIED_FIELD_NAME = "statusLastModifiedAt"; // Group public static final String CORP_GROUP_KEY_ASPECT_NAME = "corpGroupKey"; diff --git a/metadata-models/src/main/pegasus/com/linkedin/identity/CorpUserStatus.pdl b/metadata-models/src/main/pegasus/com/linkedin/identity/CorpUserStatus.pdl index fb4aba49976c46..7d4bb2dbd04cf3 100644 --- a/metadata-models/src/main/pegasus/com/linkedin/identity/CorpUserStatus.pdl +++ b/metadata-models/src/main/pegasus/com/linkedin/identity/CorpUserStatus.pdl @@ -20,5 +20,11 @@ record CorpUserStatus { /** * Audit stamp containing who last modified the status and when. */ + @Searchable = { + "/time": { + "fieldName": "statusLastModifiedAt", + "fieldType": "COUNT" + } + } lastModified: AuditStamp } diff --git a/smoke-test/tests/cypress/cypress/e2e/analytics/analytics.js b/smoke-test/tests/cypress/cypress/e2e/analytics/analytics.js index 0b8d1c443975d3..0e5105717d2bea 100644 --- a/smoke-test/tests/cypress/cypress/e2e/analytics/analytics.js +++ b/smoke-test/tests/cypress/cypress/e2e/analytics/analytics.js @@ -1,5 +1,5 @@ describe('analytics', () => { - it('can go to a chart and see analytics in Section Views', () => { + it('can go to a chart and see analytics in tab views', () => { cy.login(); cy.goToChart("urn:li:chart:(looker,cypress_baz1)"); @@ -8,7 +8,7 @@ describe('analytics', () => { cy.wait(1000); cy.goToAnalytics(); - cy.contains("Section Views across Entity Types").scrollIntoView({ + cy.contains("Tab Views By Entity Type (Past Week)").scrollIntoView({ ensureScrollable: false }) cy.waitTextPresent("dashboards");