From 43ea44a0f97702387490728e3b64d53b17d742ee Mon Sep 17 00:00:00 2001 From: Sachin Chaurasiya Date: Thu, 10 Nov 2022 14:26:04 +0530 Subject: [PATCH] =?UTF-8?q?Feat=E2=9C=A8=20(#8161)=20Implement=20logic=20t?= =?UTF-8?q?o=20fetch=20report=20data=20part-2=20(#8605)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat✨ (#8161) Implement logic to fetch report data part-2 * Fix _get_user_details should return dict not None Type * Format average session * Make user name clickable * Add provision for showing percentage symbol in graph tooltip * Add summary support for entities * Fix format issue * Fix unit tests * Fix labeling issue * Add PageViewsByEntities chart * Add DailyActiveUsers chart * Add description to charts * Fix unit tests * remove startTs Overriding * Address review comment --- .../web_analytic_report_data_processor.py | 4 +- .../ui/src/axiosAPIs/DataInsightAPI.ts | 6 +- .../DailyActiveUsersChart.tsx | 112 +++++++++++++++ .../DataInsightSummary.test.tsx | 34 ++--- .../DataInsightDetail/DataInsightSummary.tsx | 88 +++++++++--- .../DataInsightDetail/DescriptionInsight.tsx | 4 +- .../DataInsightDetail/OwnerInsight.tsx | 4 +- .../PageViewsByEntitiesChart.tsx | 132 ++++++++++++++++++ .../DataInsightDetail/TierInsight.tsx | 2 + .../DataInsightDetail/TopActiveUsers.tsx | 101 ++++++++++---- .../DataInsightDetail/TopViewEntities.tsx | 123 ++++++++-------- .../DataInsightDetail/TotalEntityInsight.tsx | 2 + .../src/interface/data-insight.interface.ts | 7 + .../ui/src/locale/languages/en-us.json | 17 ++- .../DataInsightPage.component.tsx | 14 +- .../DataInsightPage/DataInsightPage.test.tsx | 15 ++ .../ui/src/utils/DataInsightUtils.tsx | 30 +++- .../main/resources/ui/src/utils/TimeUtils.ts | 11 +- 18 files changed, 562 insertions(+), 144 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/DailyActiveUsersChart.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/PageViewsByEntitiesChart.tsx diff --git a/ingestion/src/metadata/data_insight/processor/web_analytic_report_data_processor.py b/ingestion/src/metadata/data_insight/processor/web_analytic_report_data_processor.py index 0e73b49a748..aed351573a1 100644 --- a/ingestion/src/metadata/data_insight/processor/web_analytic_report_data_processor.py +++ b/ingestion/src/metadata/data_insight/processor/web_analytic_report_data_processor.py @@ -225,7 +225,7 @@ class WebAnalyticUserActivityReportDataProcessor(DataProcessor): "totalSessionDuration": total_session_duration_seconds, } - def _get_user_details(self, user_id: str) -> Optional[dict]: + def _get_user_details(self, user_id: str) -> dict: """Get user details from user id Returns: @@ -239,7 +239,7 @@ class WebAnalyticUserActivityReportDataProcessor(DataProcessor): ) if not user_entity: - return None + return {} teams = user_entity.teams return { diff --git a/openmetadata-ui/src/main/resources/ui/src/axiosAPIs/DataInsightAPI.ts b/openmetadata-ui/src/main/resources/ui/src/axiosAPIs/DataInsightAPI.ts index e4ff253ff61..d3a6eac16ed 100644 --- a/openmetadata-ui/src/main/resources/ui/src/axiosAPIs/DataInsightAPI.ts +++ b/openmetadata-ui/src/main/resources/ui/src/axiosAPIs/DataInsightAPI.ts @@ -11,7 +11,6 @@ * limitations under the License. */ -import { DataReportIndex } from '../generated/dataInsight/dataInsightChart'; import { DataInsightChartResult } from '../generated/dataInsight/dataInsightChartResult'; import { ChartAggregateParam } from '../interface/data-insight.interface'; import APIClient from './index'; @@ -20,10 +19,7 @@ export const getAggregateChartData = async (params: ChartAggregateParam) => { const response = await APIClient.get( '/dataInsight/aggregate', { - params: { - ...params, - dataReportIndex: DataReportIndex.EntityReportDataIndex, - }, + params, } ); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/DailyActiveUsersChart.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/DailyActiveUsersChart.tsx new file mode 100644 index 00000000000..c5b4af3c89a --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/DailyActiveUsersChart.tsx @@ -0,0 +1,112 @@ +/* + * Copyright 2021 Collate + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Card, Typography } from 'antd'; +import { AxiosError } from 'axios'; +import React, { FC, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + CartesianGrid, + Line, + LineChart, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from 'recharts'; +import { getAggregateChartData } from '../../axiosAPIs/DataInsightAPI'; +import { + BAR_CHART_MARGIN, + DATA_INSIGHT_GRAPH_COLORS, +} from '../../constants/DataInsight.constants'; +import { DataReportIndex } from '../../generated/dataInsight/dataInsightChart'; +import { DataInsightChartType } from '../../generated/dataInsight/dataInsightChartResult'; +import { DailyActiveUsers } from '../../generated/dataInsight/type/dailyActiveUsers'; +import { ChartFilter } from '../../interface/data-insight.interface'; +import { + CustomTooltip, + getFormattedActiveUsersData, +} from '../../utils/DataInsightUtils'; +import { showErrorToast } from '../../utils/ToastUtils'; +import './DataInsightDetail.less'; + +interface Props { + chartFilter: ChartFilter; +} + +const DailyActiveUsersChart: FC = ({ chartFilter }) => { + const [dailyActiveUsers, setDailyActiveUsers] = useState( + [] + ); + + const [isLoading, setIsLoading] = useState(false); + + const { t } = useTranslation(); + + const fetchPageViewsByEntities = async () => { + setIsLoading(true); + try { + const params = { + ...chartFilter, + dataInsightChartName: DataInsightChartType.DailyActiveUsers, + dataReportIndex: DataReportIndex.WebAnalyticUserActivityReportDataIndex, + }; + const response = await getAggregateChartData(params); + + setDailyActiveUsers(response.data ?? []); + } catch (error) { + showErrorToast(error as AxiosError); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + fetchPageViewsByEntities(); + }, [chartFilter]); + + return ( + + + {t('label.daily-active-user')} + + + {t('message.active-users')} + + + }> + + + + + + } /> + + + + + ); +}; + +export default DailyActiveUsersChart; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/DataInsightSummary.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/DataInsightSummary.test.tsx index 5f8cd891ac9..bb504b290b0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/DataInsightSummary.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/DataInsightSummary.test.tsx @@ -13,6 +13,7 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; +import { act } from 'react-test-renderer'; import DataInsightSummary from './DataInsightSummary'; jest.mock('react-i18next', () => ({ @@ -21,33 +22,26 @@ jest.mock('react-i18next', () => ({ }), })); +const mockFilter = { + startTs: 1667952000000, + endTs: 1668000248671, +}; + +jest.mock('../../axiosAPIs/DataInsightAPI', () => ({ + getAggregateChartData: jest.fn().mockImplementation(() => Promise.resolve()), +})); + describe('Test DataInsightSummary Component', () => { it('Should render the overview data', async () => { - render(); + await act(async () => { + render(); + }); const summaryCard = screen.getByTestId('summary-card'); - const allEntityCount = screen.getByTestId('summary-item-All'); - const usersCount = screen.getByTestId('summary-item-Users'); - const sessionCount = screen.getByTestId('summary-item-Sessions'); - const activityCount = screen.getByTestId('summary-item-Activity'); - const activeUsersCount = screen.getByTestId('summary-item-ActiveUsers'); - const tablesCount = screen.getByTestId('summary-item-Tables'); - const topicsCount = screen.getByTestId('summary-item-Topics'); - const dashboardCount = screen.getByTestId('summary-item-Dashboards'); - const mlModelsCount = screen.getByTestId('summary-item-MlModels'); - const testCasesCount = screen.getByTestId('summary-item-TestCases'); + const allEntityCount = screen.getByTestId('summary-item-latest'); expect(summaryCard).toBeInTheDocument(); expect(allEntityCount).toBeInTheDocument(); - expect(usersCount).toBeInTheDocument(); - expect(sessionCount).toBeInTheDocument(); - expect(activityCount).toBeInTheDocument(); - expect(activeUsersCount).toBeInTheDocument(); - expect(tablesCount).toBeInTheDocument(); - expect(topicsCount).toBeInTheDocument(); - expect(dashboardCount).toBeInTheDocument(); - expect(mlModelsCount).toBeInTheDocument(); - expect(testCasesCount).toBeInTheDocument(); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/DataInsightSummary.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/DataInsightSummary.tsx index 25c2cdb4d01..ea389bc7141 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/DataInsightSummary.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/DataInsightSummary.tsx @@ -12,37 +12,93 @@ */ import { Card, Col, Row, Typography } from 'antd'; -import React from 'react'; +import { AxiosError } from 'axios'; +import React, { FC, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { OVERVIEW } from '../../pages/DataInsightPage/DataInsight.mock'; +import { getAggregateChartData } from '../../axiosAPIs/DataInsightAPI'; +import { DataReportIndex } from '../../generated/dataInsight/dataInsightChart'; +import { + DataInsightChartResult, + DataInsightChartType, +} from '../../generated/dataInsight/dataInsightChartResult'; +import { ChartFilter } from '../../interface/data-insight.interface'; +import { getGraphDataByEntityType } from '../../utils/DataInsightUtils'; +import { showErrorToast } from '../../utils/ToastUtils'; import './DataInsightDetail.less'; -const DataInsightSummary = () => { +interface Props { + chartFilter: ChartFilter; +} + +const DataInsightSummary: FC = ({ chartFilter }) => { + const [totalEntitiesByType, setTotalEntitiesByType] = + useState(); + + const [isLoading, setIsLoading] = useState(false); + + const { total, latestData = {} } = useMemo(() => { + return getGraphDataByEntityType( + totalEntitiesByType?.data ?? [], + DataInsightChartType.TotalEntitiesByType + ); + }, [totalEntitiesByType]); + const { t } = useTranslation(); + const fetchTotalEntitiesByType = async () => { + setIsLoading(true); + try { + const params = { + ...chartFilter, + dataInsightChartName: DataInsightChartType.TotalEntitiesByType, + dataReportIndex: DataReportIndex.EntityReportDataIndex, + }; + const response = await getAggregateChartData(params); + + setTotalEntitiesByType(response); + } catch (error) { + showErrorToast(error as AxiosError); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + fetchTotalEntitiesByType(); + }, [chartFilter]); + return ( {t('label.data-insight-summary')} }> - {OVERVIEW.map((summary, id) => ( - - - {summary.entityType} - - - {summary.count} - - - ))} + + + Latest + + {total} + + {Object.entries(latestData).map((summary) => { + const label = summary[0]; + const value = summary[1] as number; + + return label !== 'timestamp' ? ( + + + {label} + + + {value} + + + ) : null; + })} ); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/DescriptionInsight.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/DescriptionInsight.tsx index 1c2f968a581..c4ead5e474d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/DescriptionInsight.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/DescriptionInsight.tsx @@ -33,6 +33,7 @@ import { BAR_SIZE, ENTITIES_BAR_COLO_MAP, } from '../../constants/DataInsight.constants'; +import { DataReportIndex } from '../../generated/dataInsight/dataInsightChart'; import { DataInsightChartResult, DataInsightChartType, @@ -72,6 +73,7 @@ const DescriptionInsight: FC = ({ chartFilter }) => { ...chartFilter, dataInsightChartName: DataInsightChartType.PercentageOfEntitiesWithDescriptionByType, + dataReportIndex: DataReportIndex.EntityReportDataIndex, }; const response = await getAggregateChartData(params); @@ -111,7 +113,7 @@ const DescriptionInsight: FC = ({ chartFilter }) => { - } /> + } /> renderLegend(props as LegendProps, `${total}%`)} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/OwnerInsight.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/OwnerInsight.tsx index a3b5058de34..ee93ffac5e4 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/OwnerInsight.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/OwnerInsight.tsx @@ -33,6 +33,7 @@ import { BAR_SIZE, ENTITIES_BAR_COLO_MAP, } from '../../constants/DataInsight.constants'; +import { DataReportIndex } from '../../generated/dataInsight/dataInsightChart'; import { DataInsightChartResult, DataInsightChartType, @@ -72,6 +73,7 @@ const OwnerInsight: FC = ({ chartFilter }) => { ...chartFilter, dataInsightChartName: DataInsightChartType.PercentageOfEntitiesWithOwnerByType, + dataReportIndex: DataReportIndex.EntityReportDataIndex, }; const response = await getAggregateChartData(params); @@ -107,7 +109,7 @@ const OwnerInsight: FC = ({ chartFilter }) => { - } /> + } /> renderLegend(props as LegendProps, `${total}%`)} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/PageViewsByEntitiesChart.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/PageViewsByEntitiesChart.tsx new file mode 100644 index 00000000000..15a965d3f64 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/PageViewsByEntitiesChart.tsx @@ -0,0 +1,132 @@ +/* + * Copyright 2021 Collate + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Card, Typography } from 'antd'; +import { AxiosError } from 'axios'; +import { uniqueId } from 'lodash'; +import React, { FC, useEffect, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + Bar, + BarChart, + CartesianGrid, + Legend, + LegendProps, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from 'recharts'; +import { getAggregateChartData } from '../../axiosAPIs/DataInsightAPI'; +import { + BAR_CHART_MARGIN, + BAR_SIZE, + ENTITIES_BAR_COLO_MAP, +} from '../../constants/DataInsight.constants'; +import { DataReportIndex } from '../../generated/dataInsight/dataInsightChart'; +import { DataInsightChartType } from '../../generated/dataInsight/dataInsightChartResult'; +import { PageViewsByEntities } from '../../generated/dataInsight/type/pageViewsByEntities'; +import { ChartFilter } from '../../interface/data-insight.interface'; +import { + CustomTooltip, + getGraphDataByEntityType, + renderLegend, +} from '../../utils/DataInsightUtils'; +import { showErrorToast } from '../../utils/ToastUtils'; +import './DataInsightDetail.less'; + +interface Props { + chartFilter: ChartFilter; +} + +const PageViewsByEntitiesChart: FC = ({ chartFilter }) => { + const [pageViewsByEntities, setPageViewsByEntities] = + useState(); + + const [isLoading, setIsLoading] = useState(false); + + const { data, entities, total } = useMemo(() => { + return getGraphDataByEntityType( + pageViewsByEntities, + DataInsightChartType.PageViewsByEntities + ); + }, [pageViewsByEntities]); + + const { t } = useTranslation(); + + const fetchPageViewsByEntities = async () => { + setIsLoading(true); + try { + const params = { + ...chartFilter, + dataInsightChartName: DataInsightChartType.PageViewsByEntities, + dataReportIndex: DataReportIndex.WebAnalyticEntityViewReportDataIndex, + }; + const response = await getAggregateChartData(params); + + setPageViewsByEntities(response.data ?? []); + } catch (error) { + showErrorToast(error as AxiosError); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + fetchPageViewsByEntities(); + }, [chartFilter]); + + return ( + + + {t('label.page-views-by-entities')} + + + {t('message.data-insight-page-views')} + + + }> + + + + + + } /> + renderLegend(props as LegendProps, `${total}`)} + layout="vertical" + verticalAlign="top" + wrapperStyle={{ left: '0px' }} + /> + {entities.map((entity) => ( + + ))} + + + + ); +}; + +export default PageViewsByEntitiesChart; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/TierInsight.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/TierInsight.tsx index 9bd34d847c5..22b11ca66e9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/TierInsight.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/TierInsight.tsx @@ -33,6 +33,7 @@ import { BAR_SIZE, TIER_BAR_COLOR_MAP, } from '../../constants/DataInsight.constants'; +import { DataReportIndex } from '../../generated/dataInsight/dataInsightChart'; import { DataInsightChartResult, DataInsightChartType, @@ -68,6 +69,7 @@ const TierInsight: FC = ({ chartFilter }) => { const params = { ...chartFilter, dataInsightChartName: DataInsightChartType.TotalEntitiesByTier, + dataReportIndex: DataReportIndex.EntityReportDataIndex, }; const response = await getAggregateChartData(params); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/TopActiveUsers.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/TopActiveUsers.tsx index a7ffb156f57..a76934acd24 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/TopActiveUsers.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/TopActiveUsers.tsx @@ -13,24 +13,59 @@ import { Card, Space, Table, Typography } from 'antd'; import { ColumnsType } from 'antd/lib/table'; -import React, { useMemo } from 'react'; +import { AxiosError } from 'axios'; +import React, { FC, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { TOP_ACTIVE_USER } from '../../pages/DataInsightPage/DataInsight.mock'; -import { getDateTimeFromMilliSeconds } from '../../utils/TimeUtils'; +import { Link } from 'react-router-dom'; +import { getAggregateChartData } from '../../axiosAPIs/DataInsightAPI'; +import { getUserPath } from '../../constants/constants'; +import { DataReportIndex } from '../../generated/dataInsight/dataInsightChart'; +import { DataInsightChartType } from '../../generated/dataInsight/dataInsightChartResult'; +import { MostActiveUsers } from '../../generated/dataInsight/type/mostActiveUsers'; +import { ChartFilter } from '../../interface/data-insight.interface'; +import { + getDateTimeFromMilliSeconds, + getTimeDurationFromSeconds, +} from '../../utils/TimeUtils'; +import { showErrorToast } from '../../utils/ToastUtils'; import ProfilePicture from '../common/ProfilePicture/ProfilePicture'; +import Loader from '../Loader/Loader'; import './DataInsightDetail.less'; -interface ActiveUserView { - userName: string; - Team: string; - mostRecentSession: number; - totalSessions: number; - avgSessionDuration: number; + +interface Props { + chartFilter: ChartFilter; } -const TopActiveUsers = () => { +const TopActiveUsers: FC = ({ chartFilter }) => { + const [mostActiveUsers, setMostActiveUsers] = useState(); + + const [isLoading, setIsLoading] = useState(false); + const { t } = useTranslation(); - const columns: ColumnsType = useMemo( + const fetchMostActiveUsers = async () => { + setIsLoading(true); + try { + const params = { + ...chartFilter, + dataInsightChartName: DataInsightChartType.MostActiveUsers, + dataReportIndex: DataReportIndex.WebAnalyticUserActivityReportDataIndex, + }; + const response = await getAggregateChartData(params); + + setMostActiveUsers(response.data ?? []); + } catch (error) { + showErrorToast(error as AxiosError); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + fetchMostActiveUsers(); + }, [chartFilter]); + + const columns: ColumnsType = useMemo( () => [ { title: t('label.user'), @@ -39,34 +74,34 @@ const TopActiveUsers = () => { render: (userName: string) => ( - {userName} + {userName} ), }, { title: t('label.team'), - dataIndex: 'Team', - key: 'Team', - render: (Team: string) => ( - {Team ?? '--'} + dataIndex: 'team', + key: 'team', + render: (team: string) => ( + {team ?? '--'} ), }, { title: t('label.most-recent-session'), - dataIndex: 'mostRecentSession', - key: 'mostRecentSession', - render: (mostRecentSession: number) => ( + dataIndex: 'lastSession', + key: 'lastSession', + render: (lastSession: number) => ( - {getDateTimeFromMilliSeconds(mostRecentSession)} + {getDateTimeFromMilliSeconds(lastSession)} ), }, { title: t('label.total-session'), - dataIndex: 'totalSessions', - key: 'totalSessions', - render: (totalSessions: number) => ( - {totalSessions} + dataIndex: 'sessions', + key: 'sessions', + render: (sessions: number) => ( + {sessions} ), }, { @@ -74,7 +109,9 @@ const TopActiveUsers = () => { dataIndex: 'avgSessionDuration', key: 'avgSessionDuration', render: (avgSessionDuration: number) => ( - {avgSessionDuration} + + {getTimeDurationFromSeconds(avgSessionDuration)} + ), }, ], @@ -86,14 +123,20 @@ const TopActiveUsers = () => { className="data-insight-card" data-testid="entity-summary-card-percentage" title={ - - {t('label.data-insight-active-user-summary')} - + <> + + {t('label.data-insight-active-user-summary')} + + + {t('message.most-active-users')} + + }> }} pagination={false} size="small" /> diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/TopViewEntities.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/TopViewEntities.tsx index 61d4d762a6e..66ec9974b99 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/TopViewEntities.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/TopViewEntities.tsx @@ -11,81 +11,86 @@ * limitations under the License. */ -import { Card, Space, Table, Tag, Typography } from 'antd'; +import { Card, Space, Table, Typography } from 'antd'; import { ColumnsType } from 'antd/lib/table'; -import React, { useMemo } from 'react'; +import { AxiosError } from 'axios'; +import React, { FC, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { TOP_VIEW_ENTITIES } from '../../pages/DataInsightPage/DataInsight.mock'; +import { getAggregateChartData } from '../../axiosAPIs/DataInsightAPI'; +import { DataReportIndex } from '../../generated/dataInsight/dataInsightChart'; +import { DataInsightChartType } from '../../generated/dataInsight/dataInsightChartResult'; +import { MostViewedEntities } from '../../generated/dataInsight/type/mostViewedEntities'; +import { ChartFilter } from '../../interface/data-insight.interface'; +import { getDecodedFqn } from '../../utils/StringsUtils'; +import { showErrorToast } from '../../utils/ToastUtils'; import ProfilePicture from '../common/ProfilePicture/ProfilePicture'; +import Loader from '../Loader/Loader'; import './DataInsightDetail.less'; -interface EntityView { - entityName: string; - owner: string; - tags: string[]; - entityType: string; - totalViews: number; - uniqueViews: number; +interface Props { + chartFilter: ChartFilter; } -const TopViewEntities = () => { +const TopViewEntities: FC = ({ chartFilter }) => { + const [mostViewedEntities, setMostViewedEntities] = + useState(); + + const [isLoading, setIsLoading] = useState(false); + const { t } = useTranslation(); - const columns: ColumnsType = useMemo( + const fetchMostViewedEntities = async () => { + setIsLoading(true); + try { + const params = { + ...chartFilter, + dataInsightChartName: DataInsightChartType.MostViewedEntities, + dataReportIndex: DataReportIndex.WebAnalyticEntityViewReportDataIndex, + }; + const response = await getAggregateChartData(params); + + setMostViewedEntities(response.data ?? []); + } catch (error) { + showErrorToast(error as AxiosError); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + fetchMostViewedEntities(); + }, [chartFilter]); + + const columns: ColumnsType = useMemo( () => [ { title: t('label.entity-name'), - dataIndex: 'entityName', + dataIndex: 'entityFqn', key: 'entityName', - render: (entityName: string) => ( - {entityName} + render: (entityFqn: string) => ( + {getDecodedFqn(entityFqn)} ), }, { title: t('label.owner'), dataIndex: 'owner', key: 'owner', - render: (owner: string) => ( - - - {owner} - - ), - }, - { - title: t('label.tags'), - dataIndex: 'tags', - key: 'tags', - render: (tags: string[]) => ( - - {tags.map((tag, i) => ( - {tag} - ))} - - ), - }, - { - title: t('label.entity-type'), - dataIndex: 'entityType', - key: 'entityType', - render: (entityType: string) => ( - {entityType} - ), + render: (owner: string) => + owner ? ( + + + {owner} + + ) : ( + {t('label.no-owner')} + ), }, { title: t('label.total-views'), - dataIndex: 'totalViews', + dataIndex: 'pageViews', key: 'totalViews', - render: (totalViews: number) => ( - {totalViews} - ), - }, - { - title: t('label.unique-views'), - dataIndex: 'uniqueViews', - key: 'uniqueViews', - render: (uniqueViews: number) => ( - {uniqueViews} + render: (pageViews: number) => ( + {pageViews} ), }, ], @@ -97,14 +102,20 @@ const TopViewEntities = () => { className="data-insight-card" data-testid="entity-summary-card-percentage" title={ - - {t('label.data-insight-top-viewed-entity-summary')} - + <> + + {t('label.data-insight-top-viewed-entity-summary')} + + + {t('message.most-viewed-datasets')} + + }>
}} pagination={false} size="small" /> diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/TotalEntityInsight.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/TotalEntityInsight.tsx index 93bb595abef..1a5c0aaab05 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/TotalEntityInsight.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataInsightDetail/TotalEntityInsight.tsx @@ -33,6 +33,7 @@ import { BAR_SIZE, ENTITIES_BAR_COLO_MAP, } from '../../constants/DataInsight.constants'; +import { DataReportIndex } from '../../generated/dataInsight/dataInsightChart'; import { DataInsightChartResult, DataInsightChartType, @@ -71,6 +72,7 @@ const TotalEntityInsight: FC = ({ chartFilter }) => { const params = { ...chartFilter, dataInsightChartName: DataInsightChartType.TotalEntitiesByType, + dataReportIndex: DataReportIndex.EntityReportDataIndex, }; const response = await getAggregateChartData(params); diff --git a/openmetadata-ui/src/main/resources/ui/src/interface/data-insight.interface.ts b/openmetadata-ui/src/main/resources/ui/src/interface/data-insight.interface.ts index 2dc7c541921..8c7813da9a1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/interface/data-insight.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/interface/data-insight.interface.ts @@ -11,10 +11,13 @@ * limitations under the License. */ +import { TooltipProps } from 'recharts'; +import { DataReportIndex } from '../generated/dataInsight/dataInsightChart'; import { DataInsightChartType } from '../generated/dataInsight/dataInsightChartResult'; export interface ChartAggregateParam { dataInsightChartName: DataInsightChartType; + dataReportIndex: DataReportIndex; startTs: number; endTs: number; tier?: string; @@ -27,3 +30,7 @@ export interface ChartFilter { startTs: number; endTs: number; } +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export interface DataInsightChartTooltipProps extends TooltipProps { + isPercentage?: boolean; +} diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json index 1f65c9abe4b..961eb5fec73 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json @@ -178,8 +178,8 @@ "data-insight-description-summary": "Percentage of Datasets With Description", "data-insight-owner-summary": "Percentage of Datasets With Owners", "data-insight-tier-summary": "Total Datasets by Tier", - "data-insight-active-user-summary": "Top Active Users", - "data-insight-top-viewed-entity-summary": "Top Viewed Datasets", + "data-insight-active-user-summary": "Most Active Users", + "data-insight-top-viewed-entity-summary": "Most Viewed Datasets", "data-insight-total-entity-summary": "Total Datasets", "user": "User", "team": "Team", @@ -224,6 +224,9 @@ "pipeline": "Pipeline", "function": "Function", "edge-information": "Edge Information", + "no-owner": "No Owner", + "page-views-by-entities": "Page views by datasets", + "daily-active-user": "Daily active users on the platform", "collapse-all": "Collapse All", "expand-all": "Expand All", "search-lineage": "Search Lineage", @@ -248,7 +251,13 @@ "entity-restored-error": "Error while restoring {{entity}}", "no-ingestion-available": "No ingestion data available", "no-ingestion-description": "To view Ingestion Data, run the MetaData Ingestion. Please refer to this doc to schedule the", - "fetch-pipeline-status-error": "Error while fetching pipeline status." + "fetch-pipeline-status-error": "Error while fetching pipeline status.", + "data-insight-page-views": "Displays the number of time an dataset type was viewed.", + "field-insight": "Display the percentage of datasets with {{field}} by type.", + "total-entity-insight": "Display the total of datasets by type.", + "active-users": "Display the number of users active.", + "most-active-users": "Displays the most active users on the platform based on page views.", + "most-viewed-datasets": "Displays the most viewed datasets." }, "server": { "no-followed-entities": "You have not followed anything yet.", @@ -262,8 +271,6 @@ "leave-team-success": "Left the team successfully!", "join-team-error": "Error while joining the team!", "leave-team-error": "Error while leaving the team!", - "field-insight": "Display the percentage of datasets with {{field}} by type.", - "total-entity-insight": "Display the total of datasets by type.", "no-query-available": "No query available", "unexpected-response": "Unexpected response from server!" }, diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/DataInsightPage/DataInsightPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/DataInsightPage/DataInsightPage.component.tsx index 353faf4ff13..4c5fc0a6452 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/DataInsightPage/DataInsightPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/DataInsightPage/DataInsightPage.component.tsx @@ -27,9 +27,11 @@ import { searchQuery } from '../../axiosAPIs/searchAPI'; import { autocomplete } from '../../components/AdvancedSearch/AdvancedSearch.constants'; import PageLayoutV1 from '../../components/containers/PageLayoutV1'; +import DailyActiveUsersChart from '../../components/DataInsightDetail/DailyActiveUsersChart'; import DataInsightSummary from '../../components/DataInsightDetail/DataInsightSummary'; import DescriptionInsight from '../../components/DataInsightDetail/DescriptionInsight'; import OwnerInsight from '../../components/DataInsightDetail/OwnerInsight'; +import PageViewsByEntitiesChart from '../../components/DataInsightDetail/PageViewsByEntitiesChart'; import TierInsight from '../../components/DataInsightDetail/TierInsight'; import TopActiveUsers from '../../components/DataInsightDetail/TopActiveUsers'; import TopViewEntities from '../../components/DataInsightDetail/TopViewEntities'; @@ -185,7 +187,7 @@ const DataInsightPage = () => { - + { {activeTab === DATA_INSIGHT_TAB['Web Analytics'] && ( <> - + - + + + + + + + )} diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/DataInsightPage/DataInsightPage.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/DataInsightPage/DataInsightPage.test.tsx index b2f8f18417c..756b77b1880 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/DataInsightPage/DataInsightPage.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/DataInsightPage/DataInsightPage.test.tsx @@ -80,6 +80,21 @@ jest.mock('../../utils/DataInsightUtils', () => ({ ), })); +jest.mock('../../components/DataInsightDetail/DailyActiveUsersChart', () => + jest + .fn() + .mockReturnValue( +
DailyActiveUsersChart
+ ) +); +jest.mock('../../components/DataInsightDetail/PageViewsByEntitiesChart', () => + jest + .fn() + .mockReturnValue( +
PageViewsByEntitiesChart
+ ) +); + describe('Test DataInsightPage Component', () => { it('Should render all child elements', async () => { render(); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DataInsightUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/DataInsightUtils.tsx index d9205978d08..f8b21771cc1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DataInsightUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DataInsightUtils.tsx @@ -15,12 +15,14 @@ import { Card, Typography } from 'antd'; import { isInteger, last, toNumber } from 'lodash'; import React from 'react'; import { ListItem, ListValues } from 'react-awesome-query-builder'; -import { LegendProps, Surface, TooltipProps } from 'recharts'; +import { LegendProps, Surface } from 'recharts'; import { DataInsightChartResult, DataInsightChartType, } from '../generated/dataInsight/dataInsightChartResult'; +import { DailyActiveUsers } from '../generated/dataInsight/type/dailyActiveUsers'; import { TotalEntitiesByTier } from '../generated/dataInsight/type/totalEntitiesByTier'; +import { DataInsightChartTooltipProps } from '../interface/data-insight.interface'; import { getFormattedDateFromMilliSeconds } from './TimeUtils'; export const renderLegend = (legendData: LegendProps, latest: string) => { @@ -56,9 +58,11 @@ export const renderLegend = (legendData: LegendProps, latest: string) => { * we don't have type for Tooltip value and Tooltip * that's why we have to use the type "any" */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const CustomTooltip = (props: TooltipProps) => { - const { active, payload = [], label } = props; + +export const CustomTooltip = (props: DataInsightChartTooltipProps) => { + const { active, payload = [], label, isPercentage } = props; + + const suffix = isPercentage ? '%' : ''; if (active && payload && payload.length) { return ( @@ -72,7 +76,9 @@ export const CustomTooltip = (props: TooltipProps) => { {entry.dataKey} -{' '} - {isInteger(entry.value) ? entry.value : entry.value?.toFixed(2)} + {isInteger(entry.value) + ? `${entry.value}${suffix}` + : `${entry.value?.toFixed(2)}${suffix}`} ))} @@ -158,6 +164,11 @@ export const getGraphDataByEntityType = ( break; + case DataInsightChartType.PageViewsByEntities: + value = data.pageViews; + + break; + default: break; } @@ -178,6 +189,7 @@ export const getGraphDataByEntityType = ( data: graphData, entities, total: getLatestCount(latestData), + latestData, }; }; @@ -222,3 +234,11 @@ export const getTeamFilter = (suggestionValues: ListValues = []) => { value: suggestion.value, })); }; + +export const getFormattedActiveUsersData = (activeUsers: DailyActiveUsers[]) => + activeUsers.map((user) => ({ + ...user, + timestamp: user.timestamp + ? getFormattedDateFromMilliSeconds(user.timestamp) + : '', + })); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/TimeUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/TimeUtils.ts index 5d1a0226196..a86985f644c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/TimeUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/TimeUtils.ts @@ -10,8 +10,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { isNil, toNumber } from 'lodash'; -import { DateTime } from 'luxon'; +import { DateTime, Duration } from 'luxon'; const msPerSecond = 1000; const msPerMinute = 60 * msPerSecond; @@ -336,6 +337,13 @@ export const getFormattedDateFromMilliSeconds = ( export const getDateTimeFromMilliSeconds = (timeStamp: number) => DateTime.fromMillis(timeStamp).toLocaleString(DateTime.DATETIME_MED); +/** + * @param seconds EPOCH seconds + * @returns Formatted duration for valid input. Format: 00:09:31 + */ +export const getTimeDurationFromSeconds = (seconds: number) => + !isNil(seconds) ? Duration.fromObject({ seconds }).toFormat('hh:mm:ss') : ''; + /** * It takes a timestamp and returns a string in the format of "dd MMM yyyy, hh:mm" * @param {number} timeStamp - number - The timestamp you want to convert to a date. @@ -350,6 +358,7 @@ export const getDateTimeByTimeStampWithCommaSeparated = ( /** * Given a date string, return the time stamp of that date. * @param {string} date - The date you want to convert to a timestamp. + * @deprecated */ export const getTimeStampByDate = (date: string) => Date.parse(date);