mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-07-24 17:59:52 +00:00
feat#10187: added functionality for selection of custom date range for the timestamp (#11041)
* Added custom datePicker option also added yesterday option for picking the timestamp duration in data quality * localization changes for other languages * Fixed unit test failures and fixed bugs * Fixed errors on column profiler page
This commit is contained in:
parent
1957e7c964
commit
d9564ef0d5
@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 12 12"><path fill="currentColor" stroke="currentColor" stroke-width=".6" d="M6 8.66a.408.408 0 0 1-.29-.12L1.62 4.447a.409.409 0 1 1 .578-.578L6 7.672 9.802 3.87a.409.409 0 1 1 .578.578L6.29 8.54a.408.408 0 0 1-.29.12Z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 12 12"><path fill="currentColor" stroke="currentColor" stroke-width=".1" d="M6 8.66a.408.408 0 0 1-.29-.12L1.62 4.447a.409.409 0 1 1 .578-.578L6 7.672 9.802 3.87a.409.409 0 1 1 .578.578L6.29 8.54a.408.408 0 0 1-.29.12Z"/></svg>
|
Before Width: | Height: | Size: 300 B After Width: | Height: | Size: 300 B |
@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright 2023 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 { CloseCircleOutlined } from '@ant-design/icons';
|
||||
import { Button, DatePicker, Dropdown, MenuProps, Space } from 'antd';
|
||||
import { RangePickerProps } from 'antd/lib/date-picker';
|
||||
import { DateRangeObject } from 'components/ProfilerDashboard/component/TestSummary';
|
||||
import {
|
||||
DEFAULT_SELECTED_RANGE,
|
||||
PROFILER_FILTER_RANGE,
|
||||
} from 'constants/profiler.constant';
|
||||
import { MenuInfo } from 'rc-menu/lib/interface';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { getDaysCount, getTimestampLabel } from 'utils/DatePickerMenuUtils';
|
||||
import {
|
||||
getCurrentDateTimeStamp,
|
||||
getPastDatesTimeStampFromCurrentDate,
|
||||
} from 'utils/TimeUtils';
|
||||
import { ReactComponent as DropdownIcon } from '../../assets/svg/DropDown.svg';
|
||||
import './DatePickerMenu.style.less';
|
||||
|
||||
interface DatePickerMenuProps {
|
||||
showSelectedCustomRange?: boolean;
|
||||
handleDateRangeChange: (value: DateRangeObject, days?: number) => void;
|
||||
}
|
||||
|
||||
function DatePickerMenu({
|
||||
showSelectedCustomRange,
|
||||
handleDateRangeChange,
|
||||
}: DatePickerMenuProps) {
|
||||
const { t } = useTranslation();
|
||||
// State to display the label for selected range value
|
||||
const [selectedTimeRange, setSelectedTimeRange] = useState<string>(
|
||||
DEFAULT_SELECTED_RANGE.title
|
||||
);
|
||||
// state to determine the selected value to highlight in the dropdown
|
||||
const [selectedTimeRangeKey, setSelectedTimeRangeKey] = useState<
|
||||
keyof typeof PROFILER_FILTER_RANGE
|
||||
>(DEFAULT_SELECTED_RANGE.key as keyof typeof PROFILER_FILTER_RANGE);
|
||||
|
||||
const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false);
|
||||
|
||||
const handleCustomDateChange: RangePickerProps['onChange'] = (
|
||||
values,
|
||||
dateStrings
|
||||
) => {
|
||||
if (values) {
|
||||
const startTs = Date.parse(dateStrings[0]) / 1000;
|
||||
|
||||
const endTs = Date.parse(dateStrings[1]) / 1000;
|
||||
|
||||
const daysCount = getDaysCount(dateStrings[0], dateStrings[1]);
|
||||
|
||||
const selectedRangeLabel = getTimestampLabel(
|
||||
dateStrings[0],
|
||||
dateStrings[1],
|
||||
showSelectedCustomRange
|
||||
);
|
||||
|
||||
setSelectedTimeRange(selectedRangeLabel);
|
||||
setSelectedTimeRangeKey(
|
||||
'customRange' as keyof typeof PROFILER_FILTER_RANGE
|
||||
);
|
||||
setIsMenuOpen(false);
|
||||
handleDateRangeChange({ startTs, endTs }, daysCount);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOptionClick = ({ key }: MenuInfo) => {
|
||||
const selectedNumberOfDays =
|
||||
PROFILER_FILTER_RANGE[key as keyof typeof PROFILER_FILTER_RANGE].days;
|
||||
const keyString = key as keyof typeof PROFILER_FILTER_RANGE;
|
||||
const startTs = getPastDatesTimeStampFromCurrentDate(selectedNumberOfDays);
|
||||
|
||||
const endTs = getCurrentDateTimeStamp();
|
||||
|
||||
setSelectedTimeRange(PROFILER_FILTER_RANGE[keyString].title);
|
||||
setSelectedTimeRangeKey(keyString);
|
||||
setIsMenuOpen(false);
|
||||
|
||||
handleDateRangeChange({ startTs, endTs }, selectedNumberOfDays);
|
||||
};
|
||||
|
||||
const getMenuItems = () => {
|
||||
const items: MenuProps['items'] = Object.entries(PROFILER_FILTER_RANGE).map(
|
||||
([key, value]) => ({
|
||||
label: value.title,
|
||||
key,
|
||||
})
|
||||
);
|
||||
items.push({
|
||||
label: t('label.custom-range'),
|
||||
key: 'customRange',
|
||||
children: [
|
||||
{
|
||||
label: (
|
||||
<DatePicker.RangePicker
|
||||
bordered={false}
|
||||
clearIcon={<CloseCircleOutlined />}
|
||||
open={isMenuOpen}
|
||||
placement="bottomRight"
|
||||
suffixIcon={null}
|
||||
onChange={handleCustomDateChange}
|
||||
/>
|
||||
),
|
||||
key: 'datePicker',
|
||||
},
|
||||
],
|
||||
popupClassName: 'date-picker-sub-menu-popup',
|
||||
});
|
||||
|
||||
return items;
|
||||
};
|
||||
|
||||
const items: MenuProps['items'] = getMenuItems();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dropdown
|
||||
destroyPopupOnHide
|
||||
menu={{
|
||||
items,
|
||||
triggerSubMenuAction: 'click',
|
||||
onClick: handleOptionClick,
|
||||
selectedKeys: [selectedTimeRangeKey],
|
||||
}}
|
||||
open={isMenuOpen}
|
||||
trigger={['click']}
|
||||
onOpenChange={(value) => setIsMenuOpen(value)}>
|
||||
<Button>
|
||||
<Space align="center" size={8}>
|
||||
{selectedTimeRange}
|
||||
<DropdownIcon height={14} width={14} />
|
||||
</Space>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default DatePickerMenu;
|
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2023 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.
|
||||
*/
|
||||
.date-picker-sub-menu-popup {
|
||||
visibility: hidden;
|
||||
height: 0px;
|
||||
ul {
|
||||
height: 0px;
|
||||
li {
|
||||
height: 0px;
|
||||
}
|
||||
}
|
||||
}
|
@ -102,6 +102,14 @@ jest.mock('../containers/PageLayoutV1', () =>
|
||||
jest.fn().mockImplementation(({ children }) => <div>{children}</div>)
|
||||
);
|
||||
|
||||
jest.mock('components/DatePickerMenu/DatePickerMenu.component', () =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(() => (
|
||||
<div data-testid="DatePickerMenu">DatePickerMenu</div>
|
||||
))
|
||||
);
|
||||
|
||||
describe('Test ProfilerDashboardPage component', () => {
|
||||
beforeEach(() => cleanup());
|
||||
|
||||
@ -115,15 +123,13 @@ describe('Test ProfilerDashboardPage component', () => {
|
||||
const profilerSwitch = await screen.findByTestId('profiler-switch');
|
||||
const EntityPageInfo = await screen.findByText('EntityPageInfo component');
|
||||
const ProfilerTab = await screen.findByText('ProfilerTab component');
|
||||
const selectedTimeFrame = await screen.findByText(
|
||||
'label.last-number-of-days'
|
||||
);
|
||||
const DatePickerMenu = await screen.findByTestId('DatePickerMenu');
|
||||
const DataQualityTab = screen.queryByText('DataQualityTab component');
|
||||
|
||||
expect(profilerSwitch).toBeInTheDocument();
|
||||
expect(EntityPageInfo).toBeInTheDocument();
|
||||
expect(ProfilerTab).toBeInTheDocument();
|
||||
expect(selectedTimeFrame).toBeInTheDocument();
|
||||
expect(DatePickerMenu).toBeInTheDocument();
|
||||
expect(DataQualityTab).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@ -167,15 +173,13 @@ describe('Test ProfilerDashboardPage component', () => {
|
||||
const profilerSwitch = await screen.findByTestId('profiler-switch');
|
||||
const EntityPageInfo = await screen.findByText('EntityPageInfo component');
|
||||
const ProfilerTab = await screen.findByText('ProfilerTab component');
|
||||
const selectedTimeFrame = await screen.findByText(
|
||||
'label.last-number-of-days'
|
||||
);
|
||||
const DatePickerMenu = await screen.findByTestId('DatePickerMenu');
|
||||
const DataQualityTab = screen.queryByText('DataQualityTab component');
|
||||
|
||||
expect(profilerSwitch).toBeInTheDocument();
|
||||
expect(EntityPageInfo).toBeInTheDocument();
|
||||
expect(ProfilerTab).toBeInTheDocument();
|
||||
expect(selectedTimeFrame).toBeInTheDocument();
|
||||
expect(DatePickerMenu).toBeInTheDocument();
|
||||
expect(DataQualityTab).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
@ -26,6 +26,8 @@ import { RadioChangeEvent } from 'antd/lib/radio';
|
||||
import { SwitchChangeEventHandler } from 'antd/lib/switch';
|
||||
import { AxiosError } from 'axios';
|
||||
import PageLayoutV1 from 'components/containers/PageLayoutV1';
|
||||
import DatePickerMenu from 'components/DatePickerMenu/DatePickerMenu.component';
|
||||
import { isEqual } from 'lodash';
|
||||
import { EntityTags, ExtraInfo } from 'Models';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -41,7 +43,7 @@ import {
|
||||
getTeamAndUserDetailsPath,
|
||||
} from '../../constants/constants';
|
||||
import { NO_PERMISSION_FOR_ACTION } from '../../constants/HelperTextUtil';
|
||||
import { PROFILER_FILTER_RANGE } from '../../constants/profiler.constant';
|
||||
import { DEFAULT_RANGE_DATA } from '../../constants/profiler.constant';
|
||||
import { EntityType, FqnPart } from '../../enums/entity.enum';
|
||||
import { ServiceCategory } from '../../enums/service.enum';
|
||||
import { ProfilerDashboardType } from '../../enums/table.enum';
|
||||
@ -80,6 +82,7 @@ import {
|
||||
} from '../PermissionProvider/PermissionProvider.interface';
|
||||
import DataQualityTab from './component/DataQualityTab';
|
||||
import ProfilerTab from './component/ProfilerTab';
|
||||
import { DateRangeObject } from './component/TestSummary';
|
||||
import {
|
||||
ProfilerDashboardProps,
|
||||
ProfilerDashboardTab,
|
||||
@ -114,8 +117,8 @@ const ProfilerDashboard: React.FC<ProfilerDashboardProps> = ({
|
||||
);
|
||||
const [selectedTestCaseStatus, setSelectedTestCaseStatus] =
|
||||
useState<string>('');
|
||||
const [selectedTimeRange, setSelectedTimeRange] =
|
||||
useState<keyof typeof PROFILER_FILTER_RANGE>('last3days');
|
||||
const [dateRangeObject, setDateRangeObject] =
|
||||
useState<DateRangeObject>(DEFAULT_RANGE_DATA);
|
||||
const [activeColumnDetails, setActiveColumnDetails] = useState<Column>(
|
||||
{} as Column
|
||||
);
|
||||
@ -147,13 +150,6 @@ const ProfilerDashboard: React.FC<ProfilerDashboardProps> = ({
|
||||
});
|
||||
}, [dashboardType]);
|
||||
|
||||
const timeRangeOption = useMemo(() => {
|
||||
return Object.entries(PROFILER_FILTER_RANGE).map(([key, value]) => ({
|
||||
label: value.title,
|
||||
value: key,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const testCaseStatusOption = useMemo(() => {
|
||||
const testCaseStatus: Record<string, string>[] = Object.values(
|
||||
TestCaseStatus
|
||||
@ -396,11 +392,11 @@ const ProfilerDashboard: React.FC<ProfilerDashboardProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const handleTimeRangeChange = (value: keyof typeof PROFILER_FILTER_RANGE) => {
|
||||
if (value !== selectedTimeRange) {
|
||||
setSelectedTimeRange(value);
|
||||
const handleDateRangeChange = (value: DateRangeObject) => {
|
||||
if (!isEqual(value, dateRangeObject)) {
|
||||
setDateRangeObject(value);
|
||||
if (activeTab === ProfilerDashboardTab.PROFILER) {
|
||||
fetchProfilerData(entityTypeFQN, PROFILER_FILTER_RANGE[value].days);
|
||||
fetchProfilerData(entityTypeFQN, dateRangeObject);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -523,11 +519,9 @@ const ProfilerDashboard: React.FC<ProfilerDashboardProps> = ({
|
||||
</>
|
||||
)}
|
||||
{activeTab === ProfilerDashboardTab.PROFILER && (
|
||||
<Select
|
||||
className="tw-w-32"
|
||||
options={timeRangeOption}
|
||||
value={selectedTimeRange}
|
||||
onChange={handleTimeRangeChange}
|
||||
<DatePickerMenu
|
||||
showSelectedCustomRange
|
||||
handleDateRangeChange={handleDateRangeChange}
|
||||
/>
|
||||
)}
|
||||
<Tooltip
|
||||
|
@ -11,11 +11,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Col, Row, Select, Space, Typography } from 'antd';
|
||||
import { Col, Row, Space, Typography } from 'antd';
|
||||
import { AxiosError } from 'axios';
|
||||
import DatePickerMenu from 'components/DatePickerMenu/DatePickerMenu.component';
|
||||
import { t } from 'i18next';
|
||||
import { isEmpty } from 'lodash';
|
||||
import React, { ReactElement, useEffect, useMemo, useState } from 'react';
|
||||
import { isEmpty, isEqual, isUndefined } from 'lodash';
|
||||
import React, { ReactElement, useEffect, useState } from 'react';
|
||||
import {
|
||||
Legend,
|
||||
Line,
|
||||
@ -30,7 +31,7 @@ import {
|
||||
import { getListTestCaseResults } from 'rest/testAPI';
|
||||
import {
|
||||
COLORS,
|
||||
PROFILER_FILTER_RANGE,
|
||||
DEFAULT_RANGE_DATA,
|
||||
} from '../../../constants/profiler.constant';
|
||||
import { CSMode } from '../../../enums/codemirror.enum';
|
||||
import { SIZE } from '../../../enums/common.enum';
|
||||
@ -41,11 +42,7 @@ import {
|
||||
} from '../../../generated/tests/testCase';
|
||||
import { axisTickFormatter } from '../../../utils/ChartUtils';
|
||||
import { getEncodedFqn } from '../../../utils/StringsUtils';
|
||||
import {
|
||||
getCurrentDateTimeStamp,
|
||||
getFormattedDateFromSeconds,
|
||||
getPastDatesTimeStampFromCurrentDate,
|
||||
} from '../../../utils/TimeUtils';
|
||||
import { getFormattedDateFromSeconds } from '../../../utils/TimeUtils';
|
||||
import { showErrorToast } from '../../../utils/ToastUtils';
|
||||
import ErrorPlaceHolder from '../../common/error-with-placeholder/ErrorPlaceHolder';
|
||||
import RichTextEditorPreviewer from '../../common/rich-text-editor/RichTextEditorPreviewer';
|
||||
@ -58,26 +55,24 @@ type ChartDataType = {
|
||||
data: { [key: string]: string }[];
|
||||
};
|
||||
|
||||
export interface DateRangeObject {
|
||||
startTs: number;
|
||||
endTs: number;
|
||||
}
|
||||
|
||||
const TestSummary: React.FC<TestSummaryProps> = ({ data }) => {
|
||||
const [chartData, setChartData] = useState<ChartDataType>(
|
||||
{} as ChartDataType
|
||||
);
|
||||
const [results, setResults] = useState<TestCaseResult[]>([]);
|
||||
const [selectedTimeRange, setSelectedTimeRange] =
|
||||
useState<keyof typeof PROFILER_FILTER_RANGE>('last3days');
|
||||
const [dateRangeObject, setDateRangeObject] =
|
||||
useState<DateRangeObject>(DEFAULT_RANGE_DATA);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isGraphLoading, setIsGraphLoading] = useState(true);
|
||||
|
||||
const timeRangeOption = useMemo(() => {
|
||||
return Object.entries(PROFILER_FILTER_RANGE).map(([key, value]) => ({
|
||||
label: value.title,
|
||||
value: key,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const handleTimeRangeChange = (value: keyof typeof PROFILER_FILTER_RANGE) => {
|
||||
if (value !== selectedTimeRange) {
|
||||
setSelectedTimeRange(value);
|
||||
const handleDateRangeChange = (value: DateRangeObject) => {
|
||||
if (!isEqual(value, dateRangeObject)) {
|
||||
setDateRangeObject(value);
|
||||
}
|
||||
};
|
||||
|
||||
@ -97,13 +92,14 @@ const TestSummary: React.FC<TestSummaryProps> = ({ data }) => {
|
||||
...values,
|
||||
});
|
||||
});
|
||||
chartData.reverse();
|
||||
setChartData({
|
||||
information:
|
||||
currentData[0]?.testResultValue?.map((info, i) => ({
|
||||
label: info.name || '',
|
||||
color: COLORS[i],
|
||||
})) || [],
|
||||
data: chartData.reverse(),
|
||||
data: chartData,
|
||||
});
|
||||
};
|
||||
|
||||
@ -112,12 +108,12 @@ const TestSummary: React.FC<TestSummaryProps> = ({ data }) => {
|
||||
props: any
|
||||
): ReactElement<SVGElement> => {
|
||||
const { cx = 0, cy = 0, payload } = props;
|
||||
const fill =
|
||||
payload.status === TestCaseStatus.Success
|
||||
? '#28A745'
|
||||
: payload.status === TestCaseStatus.Failed
|
||||
? '#CB2431'
|
||||
: '#EFAE2F';
|
||||
let fill =
|
||||
payload.status === TestCaseStatus.Success ? '#28A745' : undefined;
|
||||
|
||||
if (isUndefined(fill)) {
|
||||
fill = payload.status === TestCaseStatus.Failed ? '#CB2431' : '#EFAE2F';
|
||||
}
|
||||
|
||||
return (
|
||||
<svg
|
||||
@ -132,24 +128,15 @@ const TestSummary: React.FC<TestSummaryProps> = ({ data }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const fetchTestResults = async () => {
|
||||
const fetchTestResults = async (dateRangeObj: DateRangeObject) => {
|
||||
if (isEmpty(data)) {
|
||||
return;
|
||||
}
|
||||
setIsGraphLoading(true);
|
||||
try {
|
||||
const startTs = getPastDatesTimeStampFromCurrentDate(
|
||||
PROFILER_FILTER_RANGE[selectedTimeRange].days
|
||||
);
|
||||
|
||||
const endTs = getCurrentDateTimeStamp();
|
||||
|
||||
const { data: chartData } = await getListTestCaseResults(
|
||||
getEncodedFqn(data.fullyQualifiedName || ''),
|
||||
{
|
||||
startTs,
|
||||
endTs,
|
||||
}
|
||||
dateRangeObj
|
||||
);
|
||||
setResults(chartData);
|
||||
generateChartData(chartData);
|
||||
@ -161,9 +148,57 @@ const TestSummary: React.FC<TestSummaryProps> = ({ data }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getGraph = () => {
|
||||
if (isGraphLoading) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
return results.length ? (
|
||||
<ResponsiveContainer
|
||||
className="tw-bg-white"
|
||||
id={`${data.name}_graph`}
|
||||
minHeight={300}>
|
||||
<LineChart
|
||||
data={chartData.data}
|
||||
margin={{
|
||||
top: 8,
|
||||
bottom: 8,
|
||||
right: 8,
|
||||
}}>
|
||||
<XAxis dataKey="name" padding={{ left: 8, right: 8 }} />
|
||||
<YAxis
|
||||
allowDataOverflow
|
||||
padding={{ top: 8, bottom: 8 }}
|
||||
tickFormatter={(value) => axisTickFormatter(value)}
|
||||
/>
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
{data.parameterValues?.length === 2 && referenceArea()}
|
||||
{chartData?.information?.map((info) => (
|
||||
<Line
|
||||
dataKey={info.label}
|
||||
dot={updatedDot}
|
||||
key={`${info.label}${info.color}`}
|
||||
stroke={info.color}
|
||||
type="monotone"
|
||||
/>
|
||||
))}
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
) : (
|
||||
<ErrorPlaceHolder classes="tw-mt-0" size={SIZE.MEDIUM}>
|
||||
<Typography.Paragraph className="m-b-md">
|
||||
{t('message.try-different-time-period-filtering')}
|
||||
</Typography.Paragraph>
|
||||
</ErrorPlaceHolder>
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchTestResults();
|
||||
}, [selectedTimeRange]);
|
||||
if (dateRangeObject) {
|
||||
fetchTestResults(dateRangeObject);
|
||||
}
|
||||
}, [dateRangeObject]);
|
||||
|
||||
const showParamsData = (param: TestCaseParameterValue) => {
|
||||
const isSqlQuery = param.name === 'sqlExpression';
|
||||
@ -217,55 +252,13 @@ const TestSummary: React.FC<TestSummaryProps> = ({ data }) => {
|
||||
) : (
|
||||
<div>
|
||||
<Space align="end" className="tw-w-full" direction="vertical">
|
||||
<Select
|
||||
className="tw-w-32 tw-mb-2"
|
||||
options={timeRangeOption}
|
||||
value={selectedTimeRange}
|
||||
onChange={handleTimeRangeChange}
|
||||
<DatePickerMenu
|
||||
showSelectedCustomRange
|
||||
handleDateRangeChange={handleDateRangeChange}
|
||||
/>
|
||||
</Space>
|
||||
|
||||
{isGraphLoading ? (
|
||||
<Loader />
|
||||
) : results.length ? (
|
||||
<ResponsiveContainer
|
||||
className="tw-bg-white"
|
||||
id={`${data.name}_graph`}
|
||||
minHeight={300}>
|
||||
<LineChart
|
||||
data={chartData.data}
|
||||
margin={{
|
||||
top: 8,
|
||||
bottom: 8,
|
||||
right: 8,
|
||||
}}>
|
||||
<XAxis dataKey="name" padding={{ left: 8, right: 8 }} />
|
||||
<YAxis
|
||||
allowDataOverflow
|
||||
padding={{ top: 8, bottom: 8 }}
|
||||
tickFormatter={(value) => axisTickFormatter(value)}
|
||||
/>
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
{data.parameterValues?.length === 2 && referenceArea()}
|
||||
{chartData?.information?.map((info, i) => (
|
||||
<Line
|
||||
dataKey={info.label}
|
||||
dot={updatedDot}
|
||||
key={i}
|
||||
stroke={info.color}
|
||||
type="monotone"
|
||||
/>
|
||||
))}
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
) : (
|
||||
<ErrorPlaceHolder classes="tw-mt-0" size={SIZE.MEDIUM}>
|
||||
<Typography.Paragraph className="m-b-md">
|
||||
{t('message.try-different-time-period-filtering')}
|
||||
</Typography.Paragraph>
|
||||
</ErrorPlaceHolder>
|
||||
)}
|
||||
{getGraph()}
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
|
@ -19,6 +19,7 @@ import {
|
||||
Table,
|
||||
} from '../../generated/entity/data/table';
|
||||
import { TestCase } from '../../generated/tests/testCase';
|
||||
import { DateRangeObject } from './component/TestSummary';
|
||||
|
||||
export interface ProfilerDashboardProps {
|
||||
onTableChange: (table: Table) => void;
|
||||
@ -26,7 +27,10 @@ export interface ProfilerDashboardProps {
|
||||
table: Table;
|
||||
testCases: TestCase[];
|
||||
profilerData: ColumnProfile[];
|
||||
fetchProfilerData: (tableId: string, days?: number) => void;
|
||||
fetchProfilerData: (
|
||||
tableId: string,
|
||||
dateRangeObject?: DateRangeObject
|
||||
) => void;
|
||||
fetchTestCases: (fqn: string, params?: ListTestCaseParams) => void;
|
||||
onTestCaseUpdate: (deleted?: boolean) => void;
|
||||
}
|
||||
|
@ -14,37 +14,20 @@
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { getSystemProfileList, getTableProfilesList } from 'rest/tableAPI';
|
||||
import {
|
||||
getPastDatesTimeStampFromCurrentDate,
|
||||
getPastDaysDateTimeMillis,
|
||||
} from '../../../utils/TimeUtils';
|
||||
import TableProfilerChart from './TableProfilerChart';
|
||||
|
||||
const mockFQN = 'testFQN';
|
||||
const mockTimeValue = {
|
||||
endSec: 1670667984,
|
||||
startSec: 1670408784,
|
||||
endMilli: 1670667985445,
|
||||
startMilli: 1670408785445,
|
||||
endMilli: 1670667984000,
|
||||
startMilli: 1670408784000,
|
||||
};
|
||||
const mockDateRangeObject = { startTs: 1670408784, endTs: 1670667984 };
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
useParams: jest.fn().mockImplementation(() => ({ datasetFQN: mockFQN })),
|
||||
}));
|
||||
jest.mock('../../../utils/TimeUtils', () => ({
|
||||
getCurrentDateTimeMillis: jest
|
||||
.fn()
|
||||
.mockImplementation(() => mockTimeValue.endMilli),
|
||||
getCurrentDateTimeStamp: jest
|
||||
.fn()
|
||||
.mockImplementation(() => mockTimeValue.endSec),
|
||||
getPastDatesTimeStampFromCurrentDate: jest
|
||||
.fn()
|
||||
.mockImplementation(() => mockTimeValue.startSec),
|
||||
getPastDaysDateTimeMillis: jest
|
||||
.fn()
|
||||
.mockImplementation(() => mockTimeValue.startMilli),
|
||||
}));
|
||||
jest.mock('rest/tableAPI');
|
||||
jest.mock('../../ProfilerDashboard/component/ProfilerLatestValue', () => {
|
||||
return jest.fn().mockImplementation(() => <div>ProfilerLatestValue</div>);
|
||||
@ -64,7 +47,7 @@ describe('TableProfilerChart component test', () => {
|
||||
const mockGetSystemProfileList = getSystemProfileList as jest.Mock;
|
||||
const mockGetTableProfilesList = getTableProfilesList as jest.Mock;
|
||||
act(() => {
|
||||
render(<TableProfilerChart selectedTimeRange="last3days" />);
|
||||
render(<TableProfilerChart dateRangeObject={mockDateRangeObject} />);
|
||||
});
|
||||
|
||||
expect(
|
||||
@ -89,7 +72,7 @@ describe('TableProfilerChart component test', () => {
|
||||
const mockGetSystemProfileList = getSystemProfileList as jest.Mock;
|
||||
const mockGetTableProfilesList = getTableProfilesList as jest.Mock;
|
||||
await act(async () => {
|
||||
render(<TableProfilerChart selectedTimeRange="last3days" />);
|
||||
render(<TableProfilerChart dateRangeObject={mockDateRangeObject} />);
|
||||
});
|
||||
|
||||
// API should be call once
|
||||
@ -111,20 +94,14 @@ describe('TableProfilerChart component test', () => {
|
||||
|
||||
it('If TimeRange change API should be call accordingly', async () => {
|
||||
const startTime = {
|
||||
inMilli: 1670063664901,
|
||||
inSec: 1670063664,
|
||||
inMilli: 1670408784000,
|
||||
inSec: 1670408784,
|
||||
};
|
||||
const mockGetSystemProfileList = getSystemProfileList as jest.Mock;
|
||||
const mockGetTableProfilesList = getTableProfilesList as jest.Mock;
|
||||
(getPastDatesTimeStampFromCurrentDate as jest.Mock).mockImplementationOnce(
|
||||
() => startTime.inSec
|
||||
);
|
||||
(getPastDaysDateTimeMillis as jest.Mock).mockImplementationOnce(
|
||||
() => startTime.inMilli
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
render(<TableProfilerChart selectedTimeRange="last7days" />);
|
||||
render(<TableProfilerChart dateRangeObject={mockDateRangeObject} />);
|
||||
});
|
||||
|
||||
// API should be call with proper Param value
|
||||
|
@ -13,24 +13,18 @@
|
||||
|
||||
import { Card, Col, Row } from 'antd';
|
||||
import { AxiosError } from 'axios';
|
||||
import { DateRangeObject } from 'components/ProfilerDashboard/component/TestSummary';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { getSystemProfileList, getTableProfilesList } from 'rest/tableAPI';
|
||||
import {
|
||||
INITIAL_OPERATION_METRIC_VALUE,
|
||||
INITIAL_ROW_METRIC_VALUE,
|
||||
PROFILER_FILTER_RANGE,
|
||||
} from '../../../constants/profiler.constant';
|
||||
import {
|
||||
calculateRowCountMetrics,
|
||||
calculateSystemMetrics,
|
||||
} from '../../../utils/TableProfilerUtils';
|
||||
import {
|
||||
getCurrentDateTimeMillis,
|
||||
getCurrentDateTimeStamp,
|
||||
getPastDatesTimeStampFromCurrentDate,
|
||||
getPastDaysDateTimeMillis,
|
||||
} from '../../../utils/TimeUtils';
|
||||
import { showErrorToast } from '../../../utils/ToastUtils';
|
||||
import CustomBarChart from '../../Chart/CustomBarChart';
|
||||
import OperationDateBarChart from '../../Chart/OperationDateBarChart';
|
||||
@ -40,7 +34,7 @@ import ProfilerLatestValue from '../../ProfilerDashboard/component/ProfilerLates
|
||||
import { MetricChartType } from '../../ProfilerDashboard/profilerDashboard.interface';
|
||||
import { TableProfilerChartProps } from '../TableProfiler.interface';
|
||||
|
||||
const TableProfilerChart = ({ selectedTimeRange }: TableProfilerChartProps) => {
|
||||
const TableProfilerChart = ({ dateRangeObject }: TableProfilerChartProps) => {
|
||||
const { datasetFQN } = useParams<{ datasetFQN: string }>();
|
||||
|
||||
const [rowCountMetrics, setRowCountMetrics] = useState<MetricChartType>(
|
||||
@ -53,28 +47,24 @@ const TableProfilerChart = ({ selectedTimeRange }: TableProfilerChartProps) => {
|
||||
useState<MetricChartType>(INITIAL_OPERATION_METRIC_VALUE);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const fetchTableProfiler = async (fqn: string, days = 3) => {
|
||||
const fetchTableProfiler = async (
|
||||
fqn: string,
|
||||
dateRangeObj: DateRangeObject
|
||||
) => {
|
||||
try {
|
||||
const startTs = getPastDatesTimeStampFromCurrentDate(days);
|
||||
const endTs = getCurrentDateTimeStamp();
|
||||
const { data } = await getTableProfilesList(fqn, {
|
||||
startTs,
|
||||
endTs,
|
||||
});
|
||||
const { data } = await getTableProfilesList(fqn, dateRangeObj);
|
||||
const rowMetricsData = calculateRowCountMetrics(data, rowCountMetrics);
|
||||
setRowCountMetrics(rowMetricsData);
|
||||
} catch (error) {
|
||||
showErrorToast(error as AxiosError);
|
||||
}
|
||||
};
|
||||
const fetchSystemProfiler = async (fqn: string, days = 3) => {
|
||||
const fetchSystemProfiler = async (
|
||||
fqn: string,
|
||||
dateRangeObj: DateRangeObject
|
||||
) => {
|
||||
try {
|
||||
const startTs = getPastDaysDateTimeMillis(days);
|
||||
const endTs = getCurrentDateTimeMillis();
|
||||
const { data } = await getSystemProfileList(fqn, {
|
||||
startTs,
|
||||
endTs,
|
||||
});
|
||||
const { data } = await getSystemProfileList(fqn, dateRangeObj);
|
||||
const { operationMetrics: metricsData, operationDateMetrics } =
|
||||
calculateSystemMetrics(data, operationMetrics);
|
||||
|
||||
@ -85,23 +75,26 @@ const TableProfilerChart = ({ selectedTimeRange }: TableProfilerChartProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
const fetchProfilerData = async (fqn: string, days = 3) => {
|
||||
const fetchProfilerData = async (
|
||||
fqn: string,
|
||||
dateRangeObj: DateRangeObject
|
||||
) => {
|
||||
setIsLoading(true);
|
||||
await fetchTableProfiler(fqn, days);
|
||||
await fetchSystemProfiler(fqn, days);
|
||||
await fetchTableProfiler(fqn, dateRangeObj);
|
||||
await fetchSystemProfiler(fqn, {
|
||||
startTs: dateRangeObj.startTs * 1000,
|
||||
endTs: dateRangeObj.endTs * 1000,
|
||||
});
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (datasetFQN) {
|
||||
fetchProfilerData(
|
||||
datasetFQN,
|
||||
PROFILER_FILTER_RANGE[selectedTimeRange].days
|
||||
);
|
||||
fetchProfilerData(datasetFQN, dateRangeObject);
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [datasetFQN, selectedTimeRange]);
|
||||
}, [datasetFQN, dateRangeObject]);
|
||||
|
||||
if (isLoading) {
|
||||
return <Loader />;
|
||||
|
@ -11,7 +11,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { PROFILER_FILTER_RANGE } from '../../constants/profiler.constant';
|
||||
import { DateRangeObject } from 'components/ProfilerDashboard/component/TestSummary';
|
||||
import { SystemProfile } from '../../generated/api/data/createTableProfile';
|
||||
import {
|
||||
Column,
|
||||
@ -82,7 +82,7 @@ export type TableProfilerData = {
|
||||
};
|
||||
|
||||
export type TableProfilerChartProps = {
|
||||
selectedTimeRange: keyof typeof PROFILER_FILTER_RANGE;
|
||||
dateRangeObject: DateRangeObject;
|
||||
};
|
||||
|
||||
export interface ProfilerSettingModalState {
|
||||
|
@ -28,7 +28,9 @@ import { DefaultOptionType } from 'antd/lib/select';
|
||||
import { SwitchChangeEventHandler } from 'antd/lib/switch';
|
||||
import { AxiosError } from 'axios';
|
||||
import classNames from 'classnames';
|
||||
import { isUndefined, map } from 'lodash';
|
||||
import DatePickerMenu from 'components/DatePickerMenu/DatePickerMenu.component';
|
||||
import { DateRangeObject } from 'components/ProfilerDashboard/component/TestSummary';
|
||||
import { isEqual, isUndefined, map } from 'lodash';
|
||||
import React, { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link, useParams } from 'react-router-dom';
|
||||
@ -42,8 +44,8 @@ import { ReactComponent as TableProfileIcon } from '../../assets/svg/table-profi
|
||||
import { API_RES_MAX_SIZE } from '../../constants/constants';
|
||||
import { PAGE_HEADERS } from '../../constants/PageHeaders.constant';
|
||||
import {
|
||||
DEFAULT_RANGE_DATA,
|
||||
INITIAL_TEST_RESULT_SUMMARY,
|
||||
PROFILER_FILTER_RANGE,
|
||||
} from '../../constants/profiler.constant';
|
||||
import { ProfilerDashboardType } from '../../enums/table.enum';
|
||||
import { ProfileSampleType, Table } from '../../generated/entity/data/table';
|
||||
@ -95,8 +97,8 @@ const TableProfilerV1: FC<TableProfilerProps> = ({
|
||||
const [selectedTestType, setSelectedTestType] = useState('');
|
||||
const [deleted, setDeleted] = useState<boolean>(false);
|
||||
const [isTestCaseLoading, setIsTestCaseLoading] = useState(false);
|
||||
const [selectedTimeRange, setSelectedTimeRange] =
|
||||
useState<keyof typeof PROFILER_FILTER_RANGE>('last3days');
|
||||
const [dateRangeObject, setDateRangeObject] =
|
||||
useState<DateRangeObject>(DEFAULT_RANGE_DATA);
|
||||
const isSummary = activeTab === ProfilerDashboardTab.SUMMARY;
|
||||
const isDataQuality = activeTab === ProfilerDashboardTab.DATA_QUALITY;
|
||||
const isProfiler = activeTab === ProfilerDashboardTab.PROFILER;
|
||||
@ -229,20 +231,13 @@ const TableProfilerV1: FC<TableProfilerProps> = ({
|
||||
},
|
||||
];
|
||||
|
||||
const timeRangeOption = useMemo(() => {
|
||||
return Object.entries(PROFILER_FILTER_RANGE).map(([key, value]) => ({
|
||||
label: value.title,
|
||||
value: key,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const handleTabChange: MenuProps['onClick'] = (value) => {
|
||||
setActiveTab(value.key as ProfilerDashboardTab);
|
||||
};
|
||||
|
||||
const handleTimeRangeChange = (value: keyof typeof PROFILER_FILTER_RANGE) => {
|
||||
if (value !== selectedTimeRange) {
|
||||
setSelectedTimeRange(value);
|
||||
const handleDateRangeChange = (value: DateRangeObject) => {
|
||||
if (!isEqual(value, dateRangeObject)) {
|
||||
setDateRangeObject(value);
|
||||
}
|
||||
};
|
||||
|
||||
@ -400,11 +395,9 @@ const TableProfilerV1: FC<TableProfilerProps> = ({
|
||||
)}
|
||||
|
||||
{isProfiler && (
|
||||
<Select
|
||||
className="tw-w-32"
|
||||
options={timeRangeOption}
|
||||
value={selectedTimeRange}
|
||||
onChange={handleTimeRangeChange}
|
||||
<DatePickerMenu
|
||||
showSelectedCustomRange
|
||||
handleDateRangeChange={handleDateRangeChange}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -515,7 +508,7 @@ const TableProfilerV1: FC<TableProfilerProps> = ({
|
||||
)}
|
||||
|
||||
{isProfiler && (
|
||||
<TableProfilerChart selectedTimeRange={selectedTimeRange} />
|
||||
<TableProfilerChart dateRangeObject={dateRangeObject} />
|
||||
)}
|
||||
|
||||
{settingModalVisible && (
|
||||
|
@ -52,7 +52,7 @@ export const DATA_INSIGHT_GRAPH_COLORS = [
|
||||
];
|
||||
|
||||
export const BAR_SIZE = 15;
|
||||
export const DEFAULT_DAYS = 30;
|
||||
export const DEFAULT_DAYS = 1;
|
||||
|
||||
export const ENTITIES_BAR_COLO_MAP: Record<string, string> = {
|
||||
Chart: '#E7B85D',
|
||||
|
@ -14,6 +14,10 @@
|
||||
import { t } from 'i18next';
|
||||
import { StepperStepType } from 'Models';
|
||||
import i18n from 'utils/i18next/LocalUtil';
|
||||
import {
|
||||
getCurrentDateTimeStamp,
|
||||
getPastDatesTimeStampFromCurrentDate,
|
||||
} from 'utils/TimeUtils';
|
||||
import { CSMode } from '../enums/codemirror.enum';
|
||||
import { DMLOperationType } from '../generated/api/data/createTableProfile';
|
||||
import {
|
||||
@ -67,6 +71,10 @@ export const PROFILER_METRIC = [
|
||||
];
|
||||
|
||||
export const PROFILER_FILTER_RANGE = {
|
||||
yesterday: {
|
||||
days: 1,
|
||||
title: t('label.yesterday'),
|
||||
},
|
||||
last3days: {
|
||||
days: 3,
|
||||
title: t('label.last-number-of-days', {
|
||||
@ -99,6 +107,18 @@ export const PROFILER_FILTER_RANGE = {
|
||||
},
|
||||
};
|
||||
|
||||
export const DEFAULT_SELECTED_RANGE = {
|
||||
key: 'yesterday',
|
||||
title: t('label.yesterday'),
|
||||
days: 1,
|
||||
};
|
||||
|
||||
export const DEFAULT_RANGE_DATA = {
|
||||
startTs: getPastDatesTimeStampFromCurrentDate(DEFAULT_SELECTED_RANGE.days),
|
||||
|
||||
endTs: getCurrentDateTimeStamp(),
|
||||
};
|
||||
|
||||
export const COLORS = ['#7147E8', '#B02AAC', '#B02AAC', '#1890FF', '#008376'];
|
||||
|
||||
export const DEFAULT_CHART_COLLECTION_VALUE = {
|
||||
|
@ -153,6 +153,7 @@
|
||||
"custom-oidc": "CustomOidc",
|
||||
"custom-property": "Custom property",
|
||||
"custom-property-plural": "Custom Properties",
|
||||
"custom-range": "Custom Range",
|
||||
"dag": "Dag",
|
||||
"dag-view": "DAG view",
|
||||
"daily-active-users-on-the-platform": "Daily Active Users on the Platform",
|
||||
@ -910,6 +911,7 @@
|
||||
"weekly-usage": "Weekly Usage",
|
||||
"whats-new": "What's New",
|
||||
"yes": "Yes",
|
||||
"yesterday": "Yesterday",
|
||||
"your-entity": "Your {{entity}}"
|
||||
},
|
||||
"message": {
|
||||
|
@ -153,6 +153,7 @@
|
||||
"custom-oidc": "OIDC personalizado",
|
||||
"custom-property": "Propiedad personalizada",
|
||||
"custom-property-plural": "Propiedades personalizadas",
|
||||
"custom-range": "Custom Range",
|
||||
"dag": "DAG",
|
||||
"dag-view": "Vista DAG",
|
||||
"daily-active-users-on-the-platform": "Usuarios activos diarios en la plataforma",
|
||||
@ -910,6 +911,7 @@
|
||||
"weekly-usage": "Uso semanal",
|
||||
"whats-new": "Novedades",
|
||||
"yes": "Sí",
|
||||
"yesterday": "Yesterday",
|
||||
"your-entity": "Tu {{entity}}"
|
||||
},
|
||||
"message": {
|
||||
|
@ -153,6 +153,7 @@
|
||||
"custom-oidc": "CustomOidc",
|
||||
"custom-property": "Custom property",
|
||||
"custom-property-plural": "Propriétés Personalisées",
|
||||
"custom-range": "Custom Range",
|
||||
"dag": "Dag",
|
||||
"dag-view": "Vue DAG",
|
||||
"daily-active-users-on-the-platform": "Utilisateurs actifs quotidiens",
|
||||
@ -910,6 +911,7 @@
|
||||
"weekly-usage": "Weekly Usage",
|
||||
"whats-new": "Nouveau",
|
||||
"yes": "Yes",
|
||||
"yesterday": "Yesterday",
|
||||
"your-entity": "Your {{entity}}"
|
||||
},
|
||||
"message": {
|
||||
|
@ -153,6 +153,7 @@
|
||||
"custom-oidc": "CustomOidc",
|
||||
"custom-property": "カスタムプロパティ",
|
||||
"custom-property-plural": "カスタムプロパティ",
|
||||
"custom-range": "Custom Range",
|
||||
"dag": "Dag",
|
||||
"dag-view": "DAGビュー",
|
||||
"daily-active-users-on-the-platform": "このプラットフォームのアクティブなユーザー",
|
||||
@ -910,6 +911,7 @@
|
||||
"weekly-usage": "Weekly Usage",
|
||||
"whats-new": "最新情報",
|
||||
"yes": "はい",
|
||||
"yesterday": "Yesterday",
|
||||
"your-entity": "あなたの{{entity}}"
|
||||
},
|
||||
"message": {
|
||||
|
@ -153,6 +153,7 @@
|
||||
"custom-oidc": "OIDC customizado",
|
||||
"custom-property": "Propriedade customizada",
|
||||
"custom-property-plural": "Propriedades customizadas",
|
||||
"custom-range": "Custom Range",
|
||||
"dag": "DAG",
|
||||
"dag-view": "Visão da DAG",
|
||||
"daily-active-users-on-the-platform": "Usuários ativos diariamente na plataforma",
|
||||
@ -910,6 +911,7 @@
|
||||
"weekly-usage": "Uso semanal",
|
||||
"whats-new": "O que há de novo",
|
||||
"yes": "Sim",
|
||||
"yesterday": "Yesterday",
|
||||
"your-entity": "Sua {{entity}}"
|
||||
},
|
||||
"message": {
|
||||
|
@ -153,6 +153,7 @@
|
||||
"custom-oidc": "CustomOidc",
|
||||
"custom-property": "Custom property",
|
||||
"custom-property-plural": "定制属性",
|
||||
"custom-range": "Custom Range",
|
||||
"dag": "Dag",
|
||||
"dag-view": "DAG view",
|
||||
"daily-active-users-on-the-platform": "Daily active users on the platform",
|
||||
@ -910,6 +911,7 @@
|
||||
"weekly-usage": "Weekly Usage",
|
||||
"whats-new": "What's new",
|
||||
"yes": "是",
|
||||
"yesterday": "Yesterday",
|
||||
"your-entity": "Your {{entity}}"
|
||||
},
|
||||
"message": {
|
||||
|
@ -11,16 +11,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import { Button, Card, Col, Row, Space, Tooltip, Typography } from 'antd';
|
||||
import PageContainerV1 from 'components/containers/PageContainerV1';
|
||||
import PageLayoutV1 from 'components/containers/PageLayoutV1';
|
||||
import DailyActiveUsersChart from 'components/DataInsightDetail/DailyActiveUsersChart';
|
||||
@ -33,11 +24,14 @@ import TierInsight from 'components/DataInsightDetail/TierInsight';
|
||||
import TopActiveUsers from 'components/DataInsightDetail/TopActiveUsers';
|
||||
import TopViewEntities from 'components/DataInsightDetail/TopViewEntities';
|
||||
import TotalEntityInsight from 'components/DataInsightDetail/TotalEntityInsight';
|
||||
import DatePickerMenu from 'components/DatePickerMenu/DatePickerMenu.component';
|
||||
import { DateRangeObject } from 'components/ProfilerDashboard/component/TestSummary';
|
||||
import SearchDropdown from 'components/SearchDropdown/SearchDropdown';
|
||||
import { SearchDropdownOption } from 'components/SearchDropdown/SearchDropdown.interface';
|
||||
import { DEFAULT_RANGE_DATA } from 'constants/profiler.constant';
|
||||
import { EntityFields } from 'enums/AdvancedSearch.enum';
|
||||
import { t } from 'i18next';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { isEmpty, isEqual } from 'lodash';
|
||||
import React, { useEffect, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { ListItem } from 'react-awesome-query-builder';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
@ -46,7 +40,6 @@ import { searchQuery } from 'rest/searchAPI';
|
||||
import { autocomplete } from '../../constants/AdvancedSearch.constants';
|
||||
import { PAGE_SIZE, ROUTES } from '../../constants/constants';
|
||||
import {
|
||||
DAY_FILTER,
|
||||
DEFAULT_DAYS,
|
||||
ENTITIES_CHARTS,
|
||||
INITIAL_CHART_FILTER,
|
||||
@ -64,11 +57,7 @@ import {
|
||||
getDataInsightPathWithFqn,
|
||||
getTeamFilter,
|
||||
} from '../../utils/DataInsightUtils';
|
||||
import {
|
||||
getCurrentDateTimeMillis,
|
||||
getFormattedDateFromMilliSeconds,
|
||||
getPastDaysDateTimeMillis,
|
||||
} from '../../utils/TimeUtils';
|
||||
import { getFormattedDateFromMilliSeconds } from '../../utils/TimeUtils';
|
||||
import { TeamStateType, TierStateType } from './DataInsight.interface';
|
||||
import './DataInsight.less';
|
||||
import DataInsightLeftPanel from './DataInsightLeftPanel';
|
||||
@ -101,6 +90,8 @@ const DataInsightPage = () => {
|
||||
useState<ChartFilter>(INITIAL_CHART_FILTER);
|
||||
const [kpiList, setKpiList] = useState<Array<Kpi>>([]);
|
||||
const [selectedDaysFilter, setSelectedDaysFilter] = useState(DEFAULT_DAYS);
|
||||
const [dateRangeObject, setDateRangeObject] =
|
||||
useState<DateRangeObject>(DEFAULT_RANGE_DATA);
|
||||
|
||||
const [selectedChart, setSelectedChart] = useState<DataInsightChartType>();
|
||||
|
||||
@ -133,13 +124,20 @@ const DataInsightPage = () => {
|
||||
}));
|
||||
};
|
||||
|
||||
const handleDaysChange = (days: number) => {
|
||||
setSelectedDaysFilter(days);
|
||||
setChartFilter((previous) => ({
|
||||
...previous,
|
||||
startTs: getPastDaysDateTimeMillis(days),
|
||||
endTs: getCurrentDateTimeMillis(),
|
||||
}));
|
||||
const handleDateRangeChange = (
|
||||
value: DateRangeObject,
|
||||
daysValue?: number
|
||||
) => {
|
||||
if (!isEqual(value, dateRangeObject)) {
|
||||
setDateRangeObject(value);
|
||||
setSelectedDaysFilter(daysValue ?? 0);
|
||||
setChartFilter((previous) => ({
|
||||
...previous,
|
||||
// Converting coming data to milliseconds
|
||||
startTs: value.startTs * 1000,
|
||||
endTs: value.endTs * 1000,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const handleTeamChange = (teams: SearchDropdownOption[] = []) => {
|
||||
@ -341,11 +339,9 @@ const DataInsightPage = () => {
|
||||
'dd MMM yyyy'
|
||||
)}`}
|
||||
</Typography>
|
||||
<Select
|
||||
className="data-insight-select-dropdown"
|
||||
defaultValue={DEFAULT_DAYS}
|
||||
options={DAY_FILTER}
|
||||
onChange={handleDaysChange}
|
||||
<DatePickerMenu
|
||||
handleDateRangeChange={handleDateRangeChange}
|
||||
showSelectedCustomRange={false}
|
||||
/>
|
||||
</Space>
|
||||
</Space>
|
||||
|
@ -20,8 +20,10 @@ import {
|
||||
OperationPermission,
|
||||
ResourceEntity,
|
||||
} from 'components/PermissionProvider/PermissionProvider.interface';
|
||||
import { DateRangeObject } from 'components/ProfilerDashboard/component/TestSummary';
|
||||
import ProfilerDashboard from 'components/ProfilerDashboard/ProfilerDashboard';
|
||||
import { ProfilerDashboardTab } from 'components/ProfilerDashboard/profilerDashboard.interface';
|
||||
import { DEFAULT_RANGE_DATA } from 'constants/profiler.constant';
|
||||
import { compare } from 'fast-json-patch';
|
||||
import { isEmpty } from 'lodash';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
@ -46,10 +48,6 @@ import {
|
||||
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
|
||||
import { getDecodedFqn } from '../../utils/StringsUtils';
|
||||
import { generateEntityLink } from '../../utils/TableUtils';
|
||||
import {
|
||||
getCurrentDateTimeStamp,
|
||||
getPastDatesTimeStampFromCurrentDate,
|
||||
} from '../../utils/TimeUtils';
|
||||
import { showErrorToast } from '../../utils/ToastUtils';
|
||||
|
||||
const ProfilerDashboardPage = () => {
|
||||
@ -89,16 +87,15 @@ const ProfilerDashboardPage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const fetchProfilerData = async (fqn: string, days = 3) => {
|
||||
const fetchProfilerData = async (
|
||||
fqn: string,
|
||||
dateRangeObject?: DateRangeObject
|
||||
) => {
|
||||
try {
|
||||
const startTs = getPastDatesTimeStampFromCurrentDate(days);
|
||||
|
||||
const endTs = getCurrentDateTimeStamp();
|
||||
|
||||
const { data } = await getColumnProfilerList(fqn, {
|
||||
startTs,
|
||||
endTs,
|
||||
});
|
||||
const { data } = await getColumnProfilerList(
|
||||
fqn,
|
||||
dateRangeObject ?? DEFAULT_RANGE_DATA
|
||||
);
|
||||
setProfilerData(data);
|
||||
} catch (error) {
|
||||
showErrorToast(error as AxiosError);
|
||||
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2023 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 { t } from 'i18next';
|
||||
|
||||
export const getTimestampLabel = (
|
||||
startDate: string,
|
||||
endDate: string,
|
||||
showSelectedCustomRange?: boolean
|
||||
) => {
|
||||
let label = t('label.custom-range');
|
||||
if (showSelectedCustomRange) {
|
||||
label += `: ${startDate} -> ${endDate}`;
|
||||
}
|
||||
|
||||
return label;
|
||||
};
|
||||
|
||||
export const getDaysCount = (startDate: string, endDate: string) => {
|
||||
const startDateObj = new Date(startDate);
|
||||
const endDateObj = new Date(endDate);
|
||||
const timeDifference = endDateObj.getTime() - startDateObj.getTime();
|
||||
|
||||
// Dividing time difference with number of milliseconds in a day to get number of days
|
||||
const numOfDays = timeDifference / (1000 * 60 * 60 * 24);
|
||||
|
||||
return numOfDays;
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user