mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-27 18:36:08 +00:00
* 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
This commit is contained in:
parent
b4e5f6ec13
commit
43ea44a0f9
@ -225,7 +225,7 @@ class WebAnalyticUserActivityReportDataProcessor(DataProcessor):
|
|||||||
"totalSessionDuration": total_session_duration_seconds,
|
"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
|
"""Get user details from user id
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -239,7 +239,7 @@ class WebAnalyticUserActivityReportDataProcessor(DataProcessor):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not user_entity:
|
if not user_entity:
|
||||||
return None
|
return {}
|
||||||
|
|
||||||
teams = user_entity.teams
|
teams = user_entity.teams
|
||||||
return {
|
return {
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DataReportIndex } from '../generated/dataInsight/dataInsightChart';
|
|
||||||
import { DataInsightChartResult } from '../generated/dataInsight/dataInsightChartResult';
|
import { DataInsightChartResult } from '../generated/dataInsight/dataInsightChartResult';
|
||||||
import { ChartAggregateParam } from '../interface/data-insight.interface';
|
import { ChartAggregateParam } from '../interface/data-insight.interface';
|
||||||
import APIClient from './index';
|
import APIClient from './index';
|
||||||
@ -20,10 +19,7 @@ export const getAggregateChartData = async (params: ChartAggregateParam) => {
|
|||||||
const response = await APIClient.get<DataInsightChartResult>(
|
const response = await APIClient.get<DataInsightChartResult>(
|
||||||
'/dataInsight/aggregate',
|
'/dataInsight/aggregate',
|
||||||
{
|
{
|
||||||
params: {
|
params,
|
||||||
...params,
|
|
||||||
dataReportIndex: DataReportIndex.EntityReportDataIndex,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -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<Props> = ({ chartFilter }) => {
|
||||||
|
const [dailyActiveUsers, setDailyActiveUsers] = useState<DailyActiveUsers[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(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 (
|
||||||
|
<Card
|
||||||
|
className="data-insight-card"
|
||||||
|
data-testid="entity-active-user-card"
|
||||||
|
loading={isLoading}
|
||||||
|
title={
|
||||||
|
<>
|
||||||
|
<Typography.Title level={5}>
|
||||||
|
{t('label.daily-active-user')}
|
||||||
|
</Typography.Title>
|
||||||
|
<Typography.Text className="data-insight-label-text">
|
||||||
|
{t('message.active-users')}
|
||||||
|
</Typography.Text>
|
||||||
|
</>
|
||||||
|
}>
|
||||||
|
<ResponsiveContainer debounce={1} minHeight={400}>
|
||||||
|
<LineChart
|
||||||
|
data={getFormattedActiveUsersData(dailyActiveUsers)}
|
||||||
|
margin={BAR_CHART_MARGIN}>
|
||||||
|
<CartesianGrid strokeDasharray="3 3" />
|
||||||
|
<XAxis dataKey="timestamp" />
|
||||||
|
<YAxis />
|
||||||
|
<Tooltip content={<CustomTooltip />} />
|
||||||
|
<Line
|
||||||
|
dataKey="activeUsers"
|
||||||
|
stroke={DATA_INSIGHT_GRAPH_COLORS[3]}
|
||||||
|
type="monotone"
|
||||||
|
/>
|
||||||
|
</LineChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DailyActiveUsersChart;
|
@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { act } from 'react-test-renderer';
|
||||||
import DataInsightSummary from './DataInsightSummary';
|
import DataInsightSummary from './DataInsightSummary';
|
||||||
|
|
||||||
jest.mock('react-i18next', () => ({
|
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', () => {
|
describe('Test DataInsightSummary Component', () => {
|
||||||
it('Should render the overview data', async () => {
|
it('Should render the overview data', async () => {
|
||||||
render(<DataInsightSummary />);
|
await act(async () => {
|
||||||
|
render(<DataInsightSummary chartFilter={mockFilter} />);
|
||||||
|
});
|
||||||
|
|
||||||
const summaryCard = screen.getByTestId('summary-card');
|
const summaryCard = screen.getByTestId('summary-card');
|
||||||
|
|
||||||
const allEntityCount = screen.getByTestId('summary-item-All');
|
const allEntityCount = screen.getByTestId('summary-item-latest');
|
||||||
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');
|
|
||||||
|
|
||||||
expect(summaryCard).toBeInTheDocument();
|
expect(summaryCard).toBeInTheDocument();
|
||||||
expect(allEntityCount).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();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -12,37 +12,93 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Card, Col, Row, Typography } from 'antd';
|
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 { 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';
|
import './DataInsightDetail.less';
|
||||||
|
|
||||||
const DataInsightSummary = () => {
|
interface Props {
|
||||||
|
chartFilter: ChartFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DataInsightSummary: FC<Props> = ({ chartFilter }) => {
|
||||||
|
const [totalEntitiesByType, setTotalEntitiesByType] =
|
||||||
|
useState<DataInsightChartResult>();
|
||||||
|
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const { total, latestData = {} } = useMemo(() => {
|
||||||
|
return getGraphDataByEntityType(
|
||||||
|
totalEntitiesByType?.data ?? [],
|
||||||
|
DataInsightChartType.TotalEntitiesByType
|
||||||
|
);
|
||||||
|
}, [totalEntitiesByType]);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
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 (
|
return (
|
||||||
<Card
|
<Card
|
||||||
className="data-insight-card"
|
className="data-insight-card"
|
||||||
data-testid="summary-card"
|
data-testid="summary-card"
|
||||||
|
loading={isLoading}
|
||||||
title={
|
title={
|
||||||
<Typography.Title level={5}>
|
<Typography.Title level={5}>
|
||||||
{t('label.data-insight-summary')}
|
{t('label.data-insight-summary')}
|
||||||
</Typography.Title>
|
</Typography.Title>
|
||||||
}>
|
}>
|
||||||
<Row data-testid="summary-card-content" gutter={[16, 16]}>
|
<Row data-testid="summary-card-content" gutter={[16, 16]}>
|
||||||
{OVERVIEW.map((summary, id) => (
|
<Col data-testid="summary-item-latest" span={4}>
|
||||||
<Col
|
<Typography.Text className="data-insight-label-text">
|
||||||
data-testid={`summary-item-${summary.entityType}`}
|
Latest
|
||||||
key={id}
|
</Typography.Text>
|
||||||
span={4}>
|
<Typography className="font-semibold text-2xl">{total}</Typography>
|
||||||
<Typography.Text className="data-insight-label-text">
|
</Col>
|
||||||
{summary.entityType}
|
{Object.entries(latestData).map((summary) => {
|
||||||
</Typography.Text>
|
const label = summary[0];
|
||||||
<Typography className="font-semibold text-2xl">
|
const value = summary[1] as number;
|
||||||
{summary.count}
|
|
||||||
</Typography>
|
return label !== 'timestamp' ? (
|
||||||
</Col>
|
<Col data-testid={`summary-item-${label}`} key={label} span={4}>
|
||||||
))}
|
<Typography.Text className="data-insight-label-text">
|
||||||
|
{label}
|
||||||
|
</Typography.Text>
|
||||||
|
<Typography className="font-semibold text-2xl">
|
||||||
|
{value}
|
||||||
|
</Typography>
|
||||||
|
</Col>
|
||||||
|
) : null;
|
||||||
|
})}
|
||||||
</Row>
|
</Row>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
@ -33,6 +33,7 @@ import {
|
|||||||
BAR_SIZE,
|
BAR_SIZE,
|
||||||
ENTITIES_BAR_COLO_MAP,
|
ENTITIES_BAR_COLO_MAP,
|
||||||
} from '../../constants/DataInsight.constants';
|
} from '../../constants/DataInsight.constants';
|
||||||
|
import { DataReportIndex } from '../../generated/dataInsight/dataInsightChart';
|
||||||
import {
|
import {
|
||||||
DataInsightChartResult,
|
DataInsightChartResult,
|
||||||
DataInsightChartType,
|
DataInsightChartType,
|
||||||
@ -72,6 +73,7 @@ const DescriptionInsight: FC<Props> = ({ chartFilter }) => {
|
|||||||
...chartFilter,
|
...chartFilter,
|
||||||
dataInsightChartName:
|
dataInsightChartName:
|
||||||
DataInsightChartType.PercentageOfEntitiesWithDescriptionByType,
|
DataInsightChartType.PercentageOfEntitiesWithDescriptionByType,
|
||||||
|
dataReportIndex: DataReportIndex.EntityReportDataIndex,
|
||||||
};
|
};
|
||||||
const response = await getAggregateChartData(params);
|
const response = await getAggregateChartData(params);
|
||||||
|
|
||||||
@ -111,7 +113,7 @@ const DescriptionInsight: FC<Props> = ({ chartFilter }) => {
|
|||||||
<XAxis dataKey="timestamp" />
|
<XAxis dataKey="timestamp" />
|
||||||
|
|
||||||
<YAxis />
|
<YAxis />
|
||||||
<Tooltip content={<CustomTooltip />} />
|
<Tooltip content={<CustomTooltip isPercentage />} />
|
||||||
<Legend
|
<Legend
|
||||||
align="left"
|
align="left"
|
||||||
content={(props) => renderLegend(props as LegendProps, `${total}%`)}
|
content={(props) => renderLegend(props as LegendProps, `${total}%`)}
|
||||||
|
@ -33,6 +33,7 @@ import {
|
|||||||
BAR_SIZE,
|
BAR_SIZE,
|
||||||
ENTITIES_BAR_COLO_MAP,
|
ENTITIES_BAR_COLO_MAP,
|
||||||
} from '../../constants/DataInsight.constants';
|
} from '../../constants/DataInsight.constants';
|
||||||
|
import { DataReportIndex } from '../../generated/dataInsight/dataInsightChart';
|
||||||
import {
|
import {
|
||||||
DataInsightChartResult,
|
DataInsightChartResult,
|
||||||
DataInsightChartType,
|
DataInsightChartType,
|
||||||
@ -72,6 +73,7 @@ const OwnerInsight: FC<Props> = ({ chartFilter }) => {
|
|||||||
...chartFilter,
|
...chartFilter,
|
||||||
dataInsightChartName:
|
dataInsightChartName:
|
||||||
DataInsightChartType.PercentageOfEntitiesWithOwnerByType,
|
DataInsightChartType.PercentageOfEntitiesWithOwnerByType,
|
||||||
|
dataReportIndex: DataReportIndex.EntityReportDataIndex,
|
||||||
};
|
};
|
||||||
const response = await getAggregateChartData(params);
|
const response = await getAggregateChartData(params);
|
||||||
|
|
||||||
@ -107,7 +109,7 @@ const OwnerInsight: FC<Props> = ({ chartFilter }) => {
|
|||||||
<CartesianGrid strokeDasharray="3 3" />
|
<CartesianGrid strokeDasharray="3 3" />
|
||||||
<XAxis dataKey="timestamp" />
|
<XAxis dataKey="timestamp" />
|
||||||
<YAxis />
|
<YAxis />
|
||||||
<Tooltip content={<CustomTooltip />} />
|
<Tooltip content={<CustomTooltip isPercentage />} />
|
||||||
<Legend
|
<Legend
|
||||||
align="left"
|
align="left"
|
||||||
content={(props) => renderLegend(props as LegendProps, `${total}%`)}
|
content={(props) => renderLegend(props as LegendProps, `${total}%`)}
|
||||||
|
@ -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<Props> = ({ chartFilter }) => {
|
||||||
|
const [pageViewsByEntities, setPageViewsByEntities] =
|
||||||
|
useState<PageViewsByEntities[]>();
|
||||||
|
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(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 (
|
||||||
|
<Card
|
||||||
|
className="data-insight-card"
|
||||||
|
data-testid="entity-page-views-card"
|
||||||
|
loading={isLoading}
|
||||||
|
title={
|
||||||
|
<>
|
||||||
|
<Typography.Title level={5}>
|
||||||
|
{t('label.page-views-by-entities')}
|
||||||
|
</Typography.Title>
|
||||||
|
<Typography.Text className="data-insight-label-text">
|
||||||
|
{t('message.data-insight-page-views')}
|
||||||
|
</Typography.Text>
|
||||||
|
</>
|
||||||
|
}>
|
||||||
|
<ResponsiveContainer debounce={1} minHeight={400}>
|
||||||
|
<BarChart data={data} margin={BAR_CHART_MARGIN}>
|
||||||
|
<CartesianGrid strokeDasharray="3 3" />
|
||||||
|
<XAxis dataKey="timestamp" />
|
||||||
|
<YAxis />
|
||||||
|
<Tooltip content={<CustomTooltip />} />
|
||||||
|
<Legend
|
||||||
|
align="left"
|
||||||
|
content={(props) => renderLegend(props as LegendProps, `${total}`)}
|
||||||
|
layout="vertical"
|
||||||
|
verticalAlign="top"
|
||||||
|
wrapperStyle={{ left: '0px' }}
|
||||||
|
/>
|
||||||
|
{entities.map((entity) => (
|
||||||
|
<Bar
|
||||||
|
barSize={BAR_SIZE}
|
||||||
|
dataKey={entity}
|
||||||
|
fill={ENTITIES_BAR_COLO_MAP[entity]}
|
||||||
|
key={uniqueId()}
|
||||||
|
stackId="entityCount"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</BarChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PageViewsByEntitiesChart;
|
@ -33,6 +33,7 @@ import {
|
|||||||
BAR_SIZE,
|
BAR_SIZE,
|
||||||
TIER_BAR_COLOR_MAP,
|
TIER_BAR_COLOR_MAP,
|
||||||
} from '../../constants/DataInsight.constants';
|
} from '../../constants/DataInsight.constants';
|
||||||
|
import { DataReportIndex } from '../../generated/dataInsight/dataInsightChart';
|
||||||
import {
|
import {
|
||||||
DataInsightChartResult,
|
DataInsightChartResult,
|
||||||
DataInsightChartType,
|
DataInsightChartType,
|
||||||
@ -68,6 +69,7 @@ const TierInsight: FC<Props> = ({ chartFilter }) => {
|
|||||||
const params = {
|
const params = {
|
||||||
...chartFilter,
|
...chartFilter,
|
||||||
dataInsightChartName: DataInsightChartType.TotalEntitiesByTier,
|
dataInsightChartName: DataInsightChartType.TotalEntitiesByTier,
|
||||||
|
dataReportIndex: DataReportIndex.EntityReportDataIndex,
|
||||||
};
|
};
|
||||||
const response = await getAggregateChartData(params);
|
const response = await getAggregateChartData(params);
|
||||||
|
|
||||||
|
@ -13,24 +13,59 @@
|
|||||||
|
|
||||||
import { Card, Space, Table, Typography } from 'antd';
|
import { Card, Space, Table, Typography } from 'antd';
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
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 { useTranslation } from 'react-i18next';
|
||||||
import { TOP_ACTIVE_USER } from '../../pages/DataInsightPage/DataInsight.mock';
|
import { Link } from 'react-router-dom';
|
||||||
import { getDateTimeFromMilliSeconds } from '../../utils/TimeUtils';
|
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 ProfilePicture from '../common/ProfilePicture/ProfilePicture';
|
||||||
|
import Loader from '../Loader/Loader';
|
||||||
import './DataInsightDetail.less';
|
import './DataInsightDetail.less';
|
||||||
interface ActiveUserView {
|
|
||||||
userName: string;
|
interface Props {
|
||||||
Team: string;
|
chartFilter: ChartFilter;
|
||||||
mostRecentSession: number;
|
|
||||||
totalSessions: number;
|
|
||||||
avgSessionDuration: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const TopActiveUsers = () => {
|
const TopActiveUsers: FC<Props> = ({ chartFilter }) => {
|
||||||
|
const [mostActiveUsers, setMostActiveUsers] = useState<MostActiveUsers[]>();
|
||||||
|
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const columns: ColumnsType<ActiveUserView> = 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<MostActiveUsers> = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
title: t('label.user'),
|
title: t('label.user'),
|
||||||
@ -39,34 +74,34 @@ const TopActiveUsers = () => {
|
|||||||
render: (userName: string) => (
|
render: (userName: string) => (
|
||||||
<Space>
|
<Space>
|
||||||
<ProfilePicture id="" name={userName} type="circle" width="24" />
|
<ProfilePicture id="" name={userName} type="circle" width="24" />
|
||||||
<Typography.Text>{userName}</Typography.Text>
|
<Link to={getUserPath(userName)}>{userName}</Link>
|
||||||
</Space>
|
</Space>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('label.team'),
|
title: t('label.team'),
|
||||||
dataIndex: 'Team',
|
dataIndex: 'team',
|
||||||
key: 'Team',
|
key: 'team',
|
||||||
render: (Team: string) => (
|
render: (team: string) => (
|
||||||
<Typography.Text>{Team ?? '--'}</Typography.Text>
|
<Typography.Text>{team ?? '--'}</Typography.Text>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('label.most-recent-session'),
|
title: t('label.most-recent-session'),
|
||||||
dataIndex: 'mostRecentSession',
|
dataIndex: 'lastSession',
|
||||||
key: 'mostRecentSession',
|
key: 'lastSession',
|
||||||
render: (mostRecentSession: number) => (
|
render: (lastSession: number) => (
|
||||||
<Typography.Text>
|
<Typography.Text>
|
||||||
{getDateTimeFromMilliSeconds(mostRecentSession)}
|
{getDateTimeFromMilliSeconds(lastSession)}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('label.total-session'),
|
title: t('label.total-session'),
|
||||||
dataIndex: 'totalSessions',
|
dataIndex: 'sessions',
|
||||||
key: 'totalSessions',
|
key: 'sessions',
|
||||||
render: (totalSessions: number) => (
|
render: (sessions: number) => (
|
||||||
<Typography.Text>{totalSessions}</Typography.Text>
|
<Typography.Text>{sessions}</Typography.Text>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -74,7 +109,9 @@ const TopActiveUsers = () => {
|
|||||||
dataIndex: 'avgSessionDuration',
|
dataIndex: 'avgSessionDuration',
|
||||||
key: 'avgSessionDuration',
|
key: 'avgSessionDuration',
|
||||||
render: (avgSessionDuration: number) => (
|
render: (avgSessionDuration: number) => (
|
||||||
<Typography.Text>{avgSessionDuration}</Typography.Text>
|
<Typography.Text>
|
||||||
|
{getTimeDurationFromSeconds(avgSessionDuration)}
|
||||||
|
</Typography.Text>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -86,14 +123,20 @@ const TopActiveUsers = () => {
|
|||||||
className="data-insight-card"
|
className="data-insight-card"
|
||||||
data-testid="entity-summary-card-percentage"
|
data-testid="entity-summary-card-percentage"
|
||||||
title={
|
title={
|
||||||
<Typography.Title level={5}>
|
<>
|
||||||
{t('label.data-insight-active-user-summary')}
|
<Typography.Title level={5}>
|
||||||
</Typography.Title>
|
{t('label.data-insight-active-user-summary')}
|
||||||
|
</Typography.Title>
|
||||||
|
<Typography.Text className="data-insight-label-text">
|
||||||
|
{t('message.most-active-users')}
|
||||||
|
</Typography.Text>
|
||||||
|
</>
|
||||||
}>
|
}>
|
||||||
<Table
|
<Table
|
||||||
className="data-insight-table-wrapper"
|
className="data-insight-table-wrapper"
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={TOP_ACTIVE_USER}
|
dataSource={mostActiveUsers}
|
||||||
|
loading={{ spinning: isLoading, indicator: <Loader /> }}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
|
@ -11,81 +11,86 @@
|
|||||||
* limitations under the License.
|
* 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 { 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 { 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 ProfilePicture from '../common/ProfilePicture/ProfilePicture';
|
||||||
|
import Loader from '../Loader/Loader';
|
||||||
import './DataInsightDetail.less';
|
import './DataInsightDetail.less';
|
||||||
|
|
||||||
interface EntityView {
|
interface Props {
|
||||||
entityName: string;
|
chartFilter: ChartFilter;
|
||||||
owner: string;
|
|
||||||
tags: string[];
|
|
||||||
entityType: string;
|
|
||||||
totalViews: number;
|
|
||||||
uniqueViews: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const TopViewEntities = () => {
|
const TopViewEntities: FC<Props> = ({ chartFilter }) => {
|
||||||
|
const [mostViewedEntities, setMostViewedEntities] =
|
||||||
|
useState<MostViewedEntities[]>();
|
||||||
|
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const columns: ColumnsType<EntityView> = 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<MostViewedEntities> = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
title: t('label.entity-name'),
|
title: t('label.entity-name'),
|
||||||
dataIndex: 'entityName',
|
dataIndex: 'entityFqn',
|
||||||
key: 'entityName',
|
key: 'entityName',
|
||||||
render: (entityName: string) => (
|
render: (entityFqn: string) => (
|
||||||
<Typography.Text>{entityName}</Typography.Text>
|
<Typography.Text>{getDecodedFqn(entityFqn)}</Typography.Text>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('label.owner'),
|
title: t('label.owner'),
|
||||||
dataIndex: 'owner',
|
dataIndex: 'owner',
|
||||||
key: 'owner',
|
key: 'owner',
|
||||||
render: (owner: string) => (
|
render: (owner: string) =>
|
||||||
<Space>
|
owner ? (
|
||||||
<ProfilePicture id="" name={owner} type="circle" width="24" />
|
<Space>
|
||||||
<Typography.Text>{owner}</Typography.Text>
|
<ProfilePicture id="" name={owner} type="circle" width="24" />
|
||||||
</Space>
|
<Typography.Text>{owner}</Typography.Text>
|
||||||
),
|
</Space>
|
||||||
},
|
) : (
|
||||||
{
|
<Typography.Text>{t('label.no-owner')}</Typography.Text>
|
||||||
title: t('label.tags'),
|
),
|
||||||
dataIndex: 'tags',
|
|
||||||
key: 'tags',
|
|
||||||
render: (tags: string[]) => (
|
|
||||||
<Typography.Text>
|
|
||||||
{tags.map((tag, i) => (
|
|
||||||
<Tag key={i}>{tag}</Tag>
|
|
||||||
))}
|
|
||||||
</Typography.Text>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t('label.entity-type'),
|
|
||||||
dataIndex: 'entityType',
|
|
||||||
key: 'entityType',
|
|
||||||
render: (entityType: string) => (
|
|
||||||
<Typography.Text>{entityType}</Typography.Text>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('label.total-views'),
|
title: t('label.total-views'),
|
||||||
dataIndex: 'totalViews',
|
dataIndex: 'pageViews',
|
||||||
key: 'totalViews',
|
key: 'totalViews',
|
||||||
render: (totalViews: number) => (
|
render: (pageViews: number) => (
|
||||||
<Typography.Text>{totalViews}</Typography.Text>
|
<Typography.Text>{pageViews}</Typography.Text>
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t('label.unique-views'),
|
|
||||||
dataIndex: 'uniqueViews',
|
|
||||||
key: 'uniqueViews',
|
|
||||||
render: (uniqueViews: number) => (
|
|
||||||
<Typography.Text>{uniqueViews}</Typography.Text>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -97,14 +102,20 @@ const TopViewEntities = () => {
|
|||||||
className="data-insight-card"
|
className="data-insight-card"
|
||||||
data-testid="entity-summary-card-percentage"
|
data-testid="entity-summary-card-percentage"
|
||||||
title={
|
title={
|
||||||
<Typography.Title level={5}>
|
<>
|
||||||
{t('label.data-insight-top-viewed-entity-summary')}
|
<Typography.Title level={5}>
|
||||||
</Typography.Title>
|
{t('label.data-insight-top-viewed-entity-summary')}
|
||||||
|
</Typography.Title>
|
||||||
|
<Typography.Text className="data-insight-label-text">
|
||||||
|
{t('message.most-viewed-datasets')}
|
||||||
|
</Typography.Text>
|
||||||
|
</>
|
||||||
}>
|
}>
|
||||||
<Table
|
<Table
|
||||||
className="data-insight-table-wrapper"
|
className="data-insight-table-wrapper"
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={TOP_VIEW_ENTITIES}
|
dataSource={mostViewedEntities}
|
||||||
|
loading={{ spinning: isLoading, indicator: <Loader /> }}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
|
@ -33,6 +33,7 @@ import {
|
|||||||
BAR_SIZE,
|
BAR_SIZE,
|
||||||
ENTITIES_BAR_COLO_MAP,
|
ENTITIES_BAR_COLO_MAP,
|
||||||
} from '../../constants/DataInsight.constants';
|
} from '../../constants/DataInsight.constants';
|
||||||
|
import { DataReportIndex } from '../../generated/dataInsight/dataInsightChart';
|
||||||
import {
|
import {
|
||||||
DataInsightChartResult,
|
DataInsightChartResult,
|
||||||
DataInsightChartType,
|
DataInsightChartType,
|
||||||
@ -71,6 +72,7 @@ const TotalEntityInsight: FC<Props> = ({ chartFilter }) => {
|
|||||||
const params = {
|
const params = {
|
||||||
...chartFilter,
|
...chartFilter,
|
||||||
dataInsightChartName: DataInsightChartType.TotalEntitiesByType,
|
dataInsightChartName: DataInsightChartType.TotalEntitiesByType,
|
||||||
|
dataReportIndex: DataReportIndex.EntityReportDataIndex,
|
||||||
};
|
};
|
||||||
const response = await getAggregateChartData(params);
|
const response = await getAggregateChartData(params);
|
||||||
|
|
||||||
|
@ -11,10 +11,13 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { TooltipProps } from 'recharts';
|
||||||
|
import { DataReportIndex } from '../generated/dataInsight/dataInsightChart';
|
||||||
import { DataInsightChartType } from '../generated/dataInsight/dataInsightChartResult';
|
import { DataInsightChartType } from '../generated/dataInsight/dataInsightChartResult';
|
||||||
|
|
||||||
export interface ChartAggregateParam {
|
export interface ChartAggregateParam {
|
||||||
dataInsightChartName: DataInsightChartType;
|
dataInsightChartName: DataInsightChartType;
|
||||||
|
dataReportIndex: DataReportIndex;
|
||||||
startTs: number;
|
startTs: number;
|
||||||
endTs: number;
|
endTs: number;
|
||||||
tier?: string;
|
tier?: string;
|
||||||
@ -27,3 +30,7 @@ export interface ChartFilter {
|
|||||||
startTs: number;
|
startTs: number;
|
||||||
endTs: number;
|
endTs: number;
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export interface DataInsightChartTooltipProps extends TooltipProps<any, any> {
|
||||||
|
isPercentage?: boolean;
|
||||||
|
}
|
||||||
|
@ -178,8 +178,8 @@
|
|||||||
"data-insight-description-summary": "Percentage of Datasets With Description",
|
"data-insight-description-summary": "Percentage of Datasets With Description",
|
||||||
"data-insight-owner-summary": "Percentage of Datasets With Owners",
|
"data-insight-owner-summary": "Percentage of Datasets With Owners",
|
||||||
"data-insight-tier-summary": "Total Datasets by Tier",
|
"data-insight-tier-summary": "Total Datasets by Tier",
|
||||||
"data-insight-active-user-summary": "Top Active Users",
|
"data-insight-active-user-summary": "Most Active Users",
|
||||||
"data-insight-top-viewed-entity-summary": "Top Viewed Datasets",
|
"data-insight-top-viewed-entity-summary": "Most Viewed Datasets",
|
||||||
"data-insight-total-entity-summary": "Total Datasets",
|
"data-insight-total-entity-summary": "Total Datasets",
|
||||||
"user": "User",
|
"user": "User",
|
||||||
"team": "Team",
|
"team": "Team",
|
||||||
@ -224,6 +224,9 @@
|
|||||||
"pipeline": "Pipeline",
|
"pipeline": "Pipeline",
|
||||||
"function": "Function",
|
"function": "Function",
|
||||||
"edge-information": "Edge Information",
|
"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",
|
"collapse-all": "Collapse All",
|
||||||
"expand-all": "Expand All",
|
"expand-all": "Expand All",
|
||||||
"search-lineage": "Search Lineage",
|
"search-lineage": "Search Lineage",
|
||||||
@ -248,7 +251,13 @@
|
|||||||
"entity-restored-error": "Error while restoring {{entity}}",
|
"entity-restored-error": "Error while restoring {{entity}}",
|
||||||
"no-ingestion-available": "No ingestion data available",
|
"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",
|
"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": {
|
"server": {
|
||||||
"no-followed-entities": "You have not followed anything yet.",
|
"no-followed-entities": "You have not followed anything yet.",
|
||||||
@ -262,8 +271,6 @@
|
|||||||
"leave-team-success": "Left the team successfully!",
|
"leave-team-success": "Left the team successfully!",
|
||||||
"join-team-error": "Error while joining the team!",
|
"join-team-error": "Error while joining the team!",
|
||||||
"leave-team-error": "Error while leaving 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",
|
"no-query-available": "No query available",
|
||||||
"unexpected-response": "Unexpected response from server!"
|
"unexpected-response": "Unexpected response from server!"
|
||||||
},
|
},
|
||||||
|
@ -27,9 +27,11 @@ import { searchQuery } from '../../axiosAPIs/searchAPI';
|
|||||||
|
|
||||||
import { autocomplete } from '../../components/AdvancedSearch/AdvancedSearch.constants';
|
import { autocomplete } from '../../components/AdvancedSearch/AdvancedSearch.constants';
|
||||||
import PageLayoutV1 from '../../components/containers/PageLayoutV1';
|
import PageLayoutV1 from '../../components/containers/PageLayoutV1';
|
||||||
|
import DailyActiveUsersChart from '../../components/DataInsightDetail/DailyActiveUsersChart';
|
||||||
import DataInsightSummary from '../../components/DataInsightDetail/DataInsightSummary';
|
import DataInsightSummary from '../../components/DataInsightDetail/DataInsightSummary';
|
||||||
import DescriptionInsight from '../../components/DataInsightDetail/DescriptionInsight';
|
import DescriptionInsight from '../../components/DataInsightDetail/DescriptionInsight';
|
||||||
import OwnerInsight from '../../components/DataInsightDetail/OwnerInsight';
|
import OwnerInsight from '../../components/DataInsightDetail/OwnerInsight';
|
||||||
|
import PageViewsByEntitiesChart from '../../components/DataInsightDetail/PageViewsByEntitiesChart';
|
||||||
import TierInsight from '../../components/DataInsightDetail/TierInsight';
|
import TierInsight from '../../components/DataInsightDetail/TierInsight';
|
||||||
import TopActiveUsers from '../../components/DataInsightDetail/TopActiveUsers';
|
import TopActiveUsers from '../../components/DataInsightDetail/TopActiveUsers';
|
||||||
import TopViewEntities from '../../components/DataInsightDetail/TopViewEntities';
|
import TopViewEntities from '../../components/DataInsightDetail/TopViewEntities';
|
||||||
@ -185,7 +187,7 @@ const DataInsightPage = () => {
|
|||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<DataInsightSummary />
|
<DataInsightSummary chartFilter={chartFilter} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<Radio.Group
|
<Radio.Group
|
||||||
@ -217,10 +219,16 @@ const DataInsightPage = () => {
|
|||||||
{activeTab === DATA_INSIGHT_TAB['Web Analytics'] && (
|
{activeTab === DATA_INSIGHT_TAB['Web Analytics'] && (
|
||||||
<>
|
<>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<TopViewEntities />
|
<TopViewEntities chartFilter={chartFilter} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<TopActiveUsers />
|
<PageViewsByEntitiesChart chartFilter={chartFilter} />
|
||||||
|
</Col>
|
||||||
|
<Col span={24}>
|
||||||
|
<TopActiveUsers chartFilter={chartFilter} />
|
||||||
|
</Col>
|
||||||
|
<Col span={24}>
|
||||||
|
<DailyActiveUsersChart chartFilter={chartFilter} />
|
||||||
</Col>
|
</Col>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -80,6 +80,21 @@ jest.mock('../../utils/DataInsightUtils', () => ({
|
|||||||
),
|
),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../components/DataInsightDetail/DailyActiveUsersChart', () =>
|
||||||
|
jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(
|
||||||
|
<div data-testid="daily-active-users">DailyActiveUsersChart</div>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
jest.mock('../../components/DataInsightDetail/PageViewsByEntitiesChart', () =>
|
||||||
|
jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(
|
||||||
|
<div data-testid="entities-page-views">PageViewsByEntitiesChart</div>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
describe('Test DataInsightPage Component', () => {
|
describe('Test DataInsightPage Component', () => {
|
||||||
it('Should render all child elements', async () => {
|
it('Should render all child elements', async () => {
|
||||||
render(<DataInsightPage />);
|
render(<DataInsightPage />);
|
||||||
|
@ -15,12 +15,14 @@ import { Card, Typography } from 'antd';
|
|||||||
import { isInteger, last, toNumber } from 'lodash';
|
import { isInteger, last, toNumber } from 'lodash';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ListItem, ListValues } from 'react-awesome-query-builder';
|
import { ListItem, ListValues } from 'react-awesome-query-builder';
|
||||||
import { LegendProps, Surface, TooltipProps } from 'recharts';
|
import { LegendProps, Surface } from 'recharts';
|
||||||
import {
|
import {
|
||||||
DataInsightChartResult,
|
DataInsightChartResult,
|
||||||
DataInsightChartType,
|
DataInsightChartType,
|
||||||
} from '../generated/dataInsight/dataInsightChartResult';
|
} from '../generated/dataInsight/dataInsightChartResult';
|
||||||
|
import { DailyActiveUsers } from '../generated/dataInsight/type/dailyActiveUsers';
|
||||||
import { TotalEntitiesByTier } from '../generated/dataInsight/type/totalEntitiesByTier';
|
import { TotalEntitiesByTier } from '../generated/dataInsight/type/totalEntitiesByTier';
|
||||||
|
import { DataInsightChartTooltipProps } from '../interface/data-insight.interface';
|
||||||
import { getFormattedDateFromMilliSeconds } from './TimeUtils';
|
import { getFormattedDateFromMilliSeconds } from './TimeUtils';
|
||||||
|
|
||||||
export const renderLegend = (legendData: LegendProps, latest: string) => {
|
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
|
* we don't have type for Tooltip value and Tooltip
|
||||||
* that's why we have to use the type "any"
|
* that's why we have to use the type "any"
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
export const CustomTooltip = (props: TooltipProps<any, any>) => {
|
export const CustomTooltip = (props: DataInsightChartTooltipProps) => {
|
||||||
const { active, payload = [], label } = props;
|
const { active, payload = [], label, isPercentage } = props;
|
||||||
|
|
||||||
|
const suffix = isPercentage ? '%' : '';
|
||||||
|
|
||||||
if (active && payload && payload.length) {
|
if (active && payload && payload.length) {
|
||||||
return (
|
return (
|
||||||
@ -72,7 +76,9 @@ export const CustomTooltip = (props: TooltipProps<any, any>) => {
|
|||||||
</Surface>
|
</Surface>
|
||||||
<span>
|
<span>
|
||||||
{entry.dataKey} -{' '}
|
{entry.dataKey} -{' '}
|
||||||
{isInteger(entry.value) ? entry.value : entry.value?.toFixed(2)}
|
{isInteger(entry.value)
|
||||||
|
? `${entry.value}${suffix}`
|
||||||
|
: `${entry.value?.toFixed(2)}${suffix}`}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
@ -158,6 +164,11 @@ export const getGraphDataByEntityType = (
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case DataInsightChartType.PageViewsByEntities:
|
||||||
|
value = data.pageViews;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -178,6 +189,7 @@ export const getGraphDataByEntityType = (
|
|||||||
data: graphData,
|
data: graphData,
|
||||||
entities,
|
entities,
|
||||||
total: getLatestCount(latestData),
|
total: getLatestCount(latestData),
|
||||||
|
latestData,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -222,3 +234,11 @@ export const getTeamFilter = (suggestionValues: ListValues = []) => {
|
|||||||
value: suggestion.value,
|
value: suggestion.value,
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getFormattedActiveUsersData = (activeUsers: DailyActiveUsers[]) =>
|
||||||
|
activeUsers.map((user) => ({
|
||||||
|
...user,
|
||||||
|
timestamp: user.timestamp
|
||||||
|
? getFormattedDateFromMilliSeconds(user.timestamp)
|
||||||
|
: '',
|
||||||
|
}));
|
||||||
|
@ -10,8 +10,9 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { isNil, toNumber } from 'lodash';
|
import { isNil, toNumber } from 'lodash';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime, Duration } from 'luxon';
|
||||||
|
|
||||||
const msPerSecond = 1000;
|
const msPerSecond = 1000;
|
||||||
const msPerMinute = 60 * msPerSecond;
|
const msPerMinute = 60 * msPerSecond;
|
||||||
@ -336,6 +337,13 @@ export const getFormattedDateFromMilliSeconds = (
|
|||||||
export const getDateTimeFromMilliSeconds = (timeStamp: number) =>
|
export const getDateTimeFromMilliSeconds = (timeStamp: number) =>
|
||||||
DateTime.fromMillis(timeStamp).toLocaleString(DateTime.DATETIME_MED);
|
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"
|
* 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.
|
* @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.
|
* Given a date string, return the time stamp of that date.
|
||||||
* @param {string} date - The date you want to convert to a timestamp.
|
* @param {string} date - The date you want to convert to a timestamp.
|
||||||
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
export const getTimeStampByDate = (date: string) => Date.parse(date);
|
export const getTimeStampByDate = (date: string) => Date.parse(date);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user