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:
Aniket Katkar 2023-04-14 01:48:59 +05:30 committed by GitHub
parent 1957e7c964
commit d9564ef0d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 436 additions and 242 deletions

View File

@ -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

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -102,6 +102,14 @@ jest.mock('../containers/PageLayoutV1', () =>
jest.fn().mockImplementation(({ children }) => <div>{children}</div>) 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', () => { describe('Test ProfilerDashboardPage component', () => {
beforeEach(() => cleanup()); beforeEach(() => cleanup());
@ -115,15 +123,13 @@ describe('Test ProfilerDashboardPage component', () => {
const profilerSwitch = await screen.findByTestId('profiler-switch'); const profilerSwitch = await screen.findByTestId('profiler-switch');
const EntityPageInfo = await screen.findByText('EntityPageInfo component'); const EntityPageInfo = await screen.findByText('EntityPageInfo component');
const ProfilerTab = await screen.findByText('ProfilerTab component'); const ProfilerTab = await screen.findByText('ProfilerTab component');
const selectedTimeFrame = await screen.findByText( const DatePickerMenu = await screen.findByTestId('DatePickerMenu');
'label.last-number-of-days'
);
const DataQualityTab = screen.queryByText('DataQualityTab component'); const DataQualityTab = screen.queryByText('DataQualityTab component');
expect(profilerSwitch).toBeInTheDocument(); expect(profilerSwitch).toBeInTheDocument();
expect(EntityPageInfo).toBeInTheDocument(); expect(EntityPageInfo).toBeInTheDocument();
expect(ProfilerTab).toBeInTheDocument(); expect(ProfilerTab).toBeInTheDocument();
expect(selectedTimeFrame).toBeInTheDocument(); expect(DatePickerMenu).toBeInTheDocument();
expect(DataQualityTab).not.toBeInTheDocument(); expect(DataQualityTab).not.toBeInTheDocument();
}); });
@ -167,15 +173,13 @@ describe('Test ProfilerDashboardPage component', () => {
const profilerSwitch = await screen.findByTestId('profiler-switch'); const profilerSwitch = await screen.findByTestId('profiler-switch');
const EntityPageInfo = await screen.findByText('EntityPageInfo component'); const EntityPageInfo = await screen.findByText('EntityPageInfo component');
const ProfilerTab = await screen.findByText('ProfilerTab component'); const ProfilerTab = await screen.findByText('ProfilerTab component');
const selectedTimeFrame = await screen.findByText( const DatePickerMenu = await screen.findByTestId('DatePickerMenu');
'label.last-number-of-days'
);
const DataQualityTab = screen.queryByText('DataQualityTab component'); const DataQualityTab = screen.queryByText('DataQualityTab component');
expect(profilerSwitch).toBeInTheDocument(); expect(profilerSwitch).toBeInTheDocument();
expect(EntityPageInfo).toBeInTheDocument(); expect(EntityPageInfo).toBeInTheDocument();
expect(ProfilerTab).toBeInTheDocument(); expect(ProfilerTab).toBeInTheDocument();
expect(selectedTimeFrame).toBeInTheDocument(); expect(DatePickerMenu).toBeInTheDocument();
expect(DataQualityTab).not.toBeInTheDocument(); expect(DataQualityTab).not.toBeInTheDocument();
}); });

View File

@ -26,6 +26,8 @@ import { RadioChangeEvent } from 'antd/lib/radio';
import { SwitchChangeEventHandler } from 'antd/lib/switch'; import { SwitchChangeEventHandler } from 'antd/lib/switch';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import PageLayoutV1 from 'components/containers/PageLayoutV1'; import PageLayoutV1 from 'components/containers/PageLayoutV1';
import DatePickerMenu from 'components/DatePickerMenu/DatePickerMenu.component';
import { isEqual } from 'lodash';
import { EntityTags, ExtraInfo } from 'Models'; import { EntityTags, ExtraInfo } from 'Models';
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -41,7 +43,7 @@ import {
getTeamAndUserDetailsPath, getTeamAndUserDetailsPath,
} from '../../constants/constants'; } from '../../constants/constants';
import { NO_PERMISSION_FOR_ACTION } from '../../constants/HelperTextUtil'; 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 { EntityType, FqnPart } from '../../enums/entity.enum';
import { ServiceCategory } from '../../enums/service.enum'; import { ServiceCategory } from '../../enums/service.enum';
import { ProfilerDashboardType } from '../../enums/table.enum'; import { ProfilerDashboardType } from '../../enums/table.enum';
@ -80,6 +82,7 @@ import {
} from '../PermissionProvider/PermissionProvider.interface'; } from '../PermissionProvider/PermissionProvider.interface';
import DataQualityTab from './component/DataQualityTab'; import DataQualityTab from './component/DataQualityTab';
import ProfilerTab from './component/ProfilerTab'; import ProfilerTab from './component/ProfilerTab';
import { DateRangeObject } from './component/TestSummary';
import { import {
ProfilerDashboardProps, ProfilerDashboardProps,
ProfilerDashboardTab, ProfilerDashboardTab,
@ -114,8 +117,8 @@ const ProfilerDashboard: React.FC<ProfilerDashboardProps> = ({
); );
const [selectedTestCaseStatus, setSelectedTestCaseStatus] = const [selectedTestCaseStatus, setSelectedTestCaseStatus] =
useState<string>(''); useState<string>('');
const [selectedTimeRange, setSelectedTimeRange] = const [dateRangeObject, setDateRangeObject] =
useState<keyof typeof PROFILER_FILTER_RANGE>('last3days'); useState<DateRangeObject>(DEFAULT_RANGE_DATA);
const [activeColumnDetails, setActiveColumnDetails] = useState<Column>( const [activeColumnDetails, setActiveColumnDetails] = useState<Column>(
{} as Column {} as Column
); );
@ -147,13 +150,6 @@ const ProfilerDashboard: React.FC<ProfilerDashboardProps> = ({
}); });
}, [dashboardType]); }, [dashboardType]);
const timeRangeOption = useMemo(() => {
return Object.entries(PROFILER_FILTER_RANGE).map(([key, value]) => ({
label: value.title,
value: key,
}));
}, []);
const testCaseStatusOption = useMemo(() => { const testCaseStatusOption = useMemo(() => {
const testCaseStatus: Record<string, string>[] = Object.values( const testCaseStatus: Record<string, string>[] = Object.values(
TestCaseStatus TestCaseStatus
@ -396,11 +392,11 @@ const ProfilerDashboard: React.FC<ProfilerDashboardProps> = ({
); );
}; };
const handleTimeRangeChange = (value: keyof typeof PROFILER_FILTER_RANGE) => { const handleDateRangeChange = (value: DateRangeObject) => {
if (value !== selectedTimeRange) { if (!isEqual(value, dateRangeObject)) {
setSelectedTimeRange(value); setDateRangeObject(value);
if (activeTab === ProfilerDashboardTab.PROFILER) { 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 && ( {activeTab === ProfilerDashboardTab.PROFILER && (
<Select <DatePickerMenu
className="tw-w-32" showSelectedCustomRange
options={timeRangeOption} handleDateRangeChange={handleDateRangeChange}
value={selectedTimeRange}
onChange={handleTimeRangeChange}
/> />
)} )}
<Tooltip <Tooltip

View File

@ -11,11 +11,12 @@
* limitations under the License. * limitations under the License.
*/ */
import { Col, Row, Select, Space, Typography } from 'antd'; import { Col, Row, Space, Typography } from 'antd';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import DatePickerMenu from 'components/DatePickerMenu/DatePickerMenu.component';
import { t } from 'i18next'; import { t } from 'i18next';
import { isEmpty } from 'lodash'; import { isEmpty, isEqual, isUndefined } from 'lodash';
import React, { ReactElement, useEffect, useMemo, useState } from 'react'; import React, { ReactElement, useEffect, useState } from 'react';
import { import {
Legend, Legend,
Line, Line,
@ -30,7 +31,7 @@ import {
import { getListTestCaseResults } from 'rest/testAPI'; import { getListTestCaseResults } from 'rest/testAPI';
import { import {
COLORS, COLORS,
PROFILER_FILTER_RANGE, DEFAULT_RANGE_DATA,
} from '../../../constants/profiler.constant'; } from '../../../constants/profiler.constant';
import { CSMode } from '../../../enums/codemirror.enum'; import { CSMode } from '../../../enums/codemirror.enum';
import { SIZE } from '../../../enums/common.enum'; import { SIZE } from '../../../enums/common.enum';
@ -41,11 +42,7 @@ import {
} from '../../../generated/tests/testCase'; } from '../../../generated/tests/testCase';
import { axisTickFormatter } from '../../../utils/ChartUtils'; import { axisTickFormatter } from '../../../utils/ChartUtils';
import { getEncodedFqn } from '../../../utils/StringsUtils'; import { getEncodedFqn } from '../../../utils/StringsUtils';
import { import { getFormattedDateFromSeconds } from '../../../utils/TimeUtils';
getCurrentDateTimeStamp,
getFormattedDateFromSeconds,
getPastDatesTimeStampFromCurrentDate,
} from '../../../utils/TimeUtils';
import { showErrorToast } from '../../../utils/ToastUtils'; import { showErrorToast } from '../../../utils/ToastUtils';
import ErrorPlaceHolder from '../../common/error-with-placeholder/ErrorPlaceHolder'; import ErrorPlaceHolder from '../../common/error-with-placeholder/ErrorPlaceHolder';
import RichTextEditorPreviewer from '../../common/rich-text-editor/RichTextEditorPreviewer'; import RichTextEditorPreviewer from '../../common/rich-text-editor/RichTextEditorPreviewer';
@ -58,26 +55,24 @@ type ChartDataType = {
data: { [key: string]: string }[]; data: { [key: string]: string }[];
}; };
export interface DateRangeObject {
startTs: number;
endTs: number;
}
const TestSummary: React.FC<TestSummaryProps> = ({ data }) => { const TestSummary: React.FC<TestSummaryProps> = ({ data }) => {
const [chartData, setChartData] = useState<ChartDataType>( const [chartData, setChartData] = useState<ChartDataType>(
{} as ChartDataType {} as ChartDataType
); );
const [results, setResults] = useState<TestCaseResult[]>([]); const [results, setResults] = useState<TestCaseResult[]>([]);
const [selectedTimeRange, setSelectedTimeRange] = const [dateRangeObject, setDateRangeObject] =
useState<keyof typeof PROFILER_FILTER_RANGE>('last3days'); useState<DateRangeObject>(DEFAULT_RANGE_DATA);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [isGraphLoading, setIsGraphLoading] = useState(true); const [isGraphLoading, setIsGraphLoading] = useState(true);
const timeRangeOption = useMemo(() => { const handleDateRangeChange = (value: DateRangeObject) => {
return Object.entries(PROFILER_FILTER_RANGE).map(([key, value]) => ({ if (!isEqual(value, dateRangeObject)) {
label: value.title, setDateRangeObject(value);
value: key,
}));
}, []);
const handleTimeRangeChange = (value: keyof typeof PROFILER_FILTER_RANGE) => {
if (value !== selectedTimeRange) {
setSelectedTimeRange(value);
} }
}; };
@ -97,13 +92,14 @@ const TestSummary: React.FC<TestSummaryProps> = ({ data }) => {
...values, ...values,
}); });
}); });
chartData.reverse();
setChartData({ setChartData({
information: information:
currentData[0]?.testResultValue?.map((info, i) => ({ currentData[0]?.testResultValue?.map((info, i) => ({
label: info.name || '', label: info.name || '',
color: COLORS[i], color: COLORS[i],
})) || [], })) || [],
data: chartData.reverse(), data: chartData,
}); });
}; };
@ -112,12 +108,12 @@ const TestSummary: React.FC<TestSummaryProps> = ({ data }) => {
props: any props: any
): ReactElement<SVGElement> => { ): ReactElement<SVGElement> => {
const { cx = 0, cy = 0, payload } = props; const { cx = 0, cy = 0, payload } = props;
const fill = let fill =
payload.status === TestCaseStatus.Success payload.status === TestCaseStatus.Success ? '#28A745' : undefined;
? '#28A745'
: payload.status === TestCaseStatus.Failed if (isUndefined(fill)) {
? '#CB2431' fill = payload.status === TestCaseStatus.Failed ? '#CB2431' : '#EFAE2F';
: '#EFAE2F'; }
return ( return (
<svg <svg
@ -132,24 +128,15 @@ const TestSummary: React.FC<TestSummaryProps> = ({ data }) => {
); );
}; };
const fetchTestResults = async () => { const fetchTestResults = async (dateRangeObj: DateRangeObject) => {
if (isEmpty(data)) { if (isEmpty(data)) {
return; return;
} }
setIsGraphLoading(true); setIsGraphLoading(true);
try { try {
const startTs = getPastDatesTimeStampFromCurrentDate(
PROFILER_FILTER_RANGE[selectedTimeRange].days
);
const endTs = getCurrentDateTimeStamp();
const { data: chartData } = await getListTestCaseResults( const { data: chartData } = await getListTestCaseResults(
getEncodedFqn(data.fullyQualifiedName || ''), getEncodedFqn(data.fullyQualifiedName || ''),
{ dateRangeObj
startTs,
endTs,
}
); );
setResults(chartData); setResults(chartData);
generateChartData(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(() => { useEffect(() => {
fetchTestResults(); if (dateRangeObject) {
}, [selectedTimeRange]); fetchTestResults(dateRangeObject);
}
}, [dateRangeObject]);
const showParamsData = (param: TestCaseParameterValue) => { const showParamsData = (param: TestCaseParameterValue) => {
const isSqlQuery = param.name === 'sqlExpression'; const isSqlQuery = param.name === 'sqlExpression';
@ -217,55 +252,13 @@ const TestSummary: React.FC<TestSummaryProps> = ({ data }) => {
) : ( ) : (
<div> <div>
<Space align="end" className="tw-w-full" direction="vertical"> <Space align="end" className="tw-w-full" direction="vertical">
<Select <DatePickerMenu
className="tw-w-32 tw-mb-2" showSelectedCustomRange
options={timeRangeOption} handleDateRangeChange={handleDateRangeChange}
value={selectedTimeRange}
onChange={handleTimeRangeChange}
/> />
</Space> </Space>
{isGraphLoading ? ( {getGraph()}
<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>
)}
</div> </div>
)} )}
</Col> </Col>

View File

@ -19,6 +19,7 @@ import {
Table, Table,
} from '../../generated/entity/data/table'; } from '../../generated/entity/data/table';
import { TestCase } from '../../generated/tests/testCase'; import { TestCase } from '../../generated/tests/testCase';
import { DateRangeObject } from './component/TestSummary';
export interface ProfilerDashboardProps { export interface ProfilerDashboardProps {
onTableChange: (table: Table) => void; onTableChange: (table: Table) => void;
@ -26,7 +27,10 @@ export interface ProfilerDashboardProps {
table: Table; table: Table;
testCases: TestCase[]; testCases: TestCase[];
profilerData: ColumnProfile[]; profilerData: ColumnProfile[];
fetchProfilerData: (tableId: string, days?: number) => void; fetchProfilerData: (
tableId: string,
dateRangeObject?: DateRangeObject
) => void;
fetchTestCases: (fqn: string, params?: ListTestCaseParams) => void; fetchTestCases: (fqn: string, params?: ListTestCaseParams) => void;
onTestCaseUpdate: (deleted?: boolean) => void; onTestCaseUpdate: (deleted?: boolean) => void;
} }

View File

@ -14,37 +14,20 @@
import { act, render, screen } from '@testing-library/react'; import { act, render, screen } from '@testing-library/react';
import React from 'react'; import React from 'react';
import { getSystemProfileList, getTableProfilesList } from 'rest/tableAPI'; import { getSystemProfileList, getTableProfilesList } from 'rest/tableAPI';
import {
getPastDatesTimeStampFromCurrentDate,
getPastDaysDateTimeMillis,
} from '../../../utils/TimeUtils';
import TableProfilerChart from './TableProfilerChart'; import TableProfilerChart from './TableProfilerChart';
const mockFQN = 'testFQN'; const mockFQN = 'testFQN';
const mockTimeValue = { const mockTimeValue = {
endSec: 1670667984, endSec: 1670667984,
startSec: 1670408784, startSec: 1670408784,
endMilli: 1670667985445, endMilli: 1670667984000,
startMilli: 1670408785445, startMilli: 1670408784000,
}; };
const mockDateRangeObject = { startTs: 1670408784, endTs: 1670667984 };
jest.mock('react-router-dom', () => ({ jest.mock('react-router-dom', () => ({
useParams: jest.fn().mockImplementation(() => ({ datasetFQN: mockFQN })), 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('rest/tableAPI');
jest.mock('../../ProfilerDashboard/component/ProfilerLatestValue', () => { jest.mock('../../ProfilerDashboard/component/ProfilerLatestValue', () => {
return jest.fn().mockImplementation(() => <div>ProfilerLatestValue</div>); return jest.fn().mockImplementation(() => <div>ProfilerLatestValue</div>);
@ -64,7 +47,7 @@ describe('TableProfilerChart component test', () => {
const mockGetSystemProfileList = getSystemProfileList as jest.Mock; const mockGetSystemProfileList = getSystemProfileList as jest.Mock;
const mockGetTableProfilesList = getTableProfilesList as jest.Mock; const mockGetTableProfilesList = getTableProfilesList as jest.Mock;
act(() => { act(() => {
render(<TableProfilerChart selectedTimeRange="last3days" />); render(<TableProfilerChart dateRangeObject={mockDateRangeObject} />);
}); });
expect( expect(
@ -89,7 +72,7 @@ describe('TableProfilerChart component test', () => {
const mockGetSystemProfileList = getSystemProfileList as jest.Mock; const mockGetSystemProfileList = getSystemProfileList as jest.Mock;
const mockGetTableProfilesList = getTableProfilesList as jest.Mock; const mockGetTableProfilesList = getTableProfilesList as jest.Mock;
await act(async () => { await act(async () => {
render(<TableProfilerChart selectedTimeRange="last3days" />); render(<TableProfilerChart dateRangeObject={mockDateRangeObject} />);
}); });
// API should be call once // API should be call once
@ -111,20 +94,14 @@ describe('TableProfilerChart component test', () => {
it('If TimeRange change API should be call accordingly', async () => { it('If TimeRange change API should be call accordingly', async () => {
const startTime = { const startTime = {
inMilli: 1670063664901, inMilli: 1670408784000,
inSec: 1670063664, inSec: 1670408784,
}; };
const mockGetSystemProfileList = getSystemProfileList as jest.Mock; const mockGetSystemProfileList = getSystemProfileList as jest.Mock;
const mockGetTableProfilesList = getTableProfilesList 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 () => { await act(async () => {
render(<TableProfilerChart selectedTimeRange="last7days" />); render(<TableProfilerChart dateRangeObject={mockDateRangeObject} />);
}); });
// API should be call with proper Param value // API should be call with proper Param value

View File

@ -13,24 +13,18 @@
import { Card, Col, Row } from 'antd'; import { Card, Col, Row } from 'antd';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { DateRangeObject } from 'components/ProfilerDashboard/component/TestSummary';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { getSystemProfileList, getTableProfilesList } from 'rest/tableAPI'; import { getSystemProfileList, getTableProfilesList } from 'rest/tableAPI';
import { import {
INITIAL_OPERATION_METRIC_VALUE, INITIAL_OPERATION_METRIC_VALUE,
INITIAL_ROW_METRIC_VALUE, INITIAL_ROW_METRIC_VALUE,
PROFILER_FILTER_RANGE,
} from '../../../constants/profiler.constant'; } from '../../../constants/profiler.constant';
import { import {
calculateRowCountMetrics, calculateRowCountMetrics,
calculateSystemMetrics, calculateSystemMetrics,
} from '../../../utils/TableProfilerUtils'; } from '../../../utils/TableProfilerUtils';
import {
getCurrentDateTimeMillis,
getCurrentDateTimeStamp,
getPastDatesTimeStampFromCurrentDate,
getPastDaysDateTimeMillis,
} from '../../../utils/TimeUtils';
import { showErrorToast } from '../../../utils/ToastUtils'; import { showErrorToast } from '../../../utils/ToastUtils';
import CustomBarChart from '../../Chart/CustomBarChart'; import CustomBarChart from '../../Chart/CustomBarChart';
import OperationDateBarChart from '../../Chart/OperationDateBarChart'; import OperationDateBarChart from '../../Chart/OperationDateBarChart';
@ -40,7 +34,7 @@ import ProfilerLatestValue from '../../ProfilerDashboard/component/ProfilerLates
import { MetricChartType } from '../../ProfilerDashboard/profilerDashboard.interface'; import { MetricChartType } from '../../ProfilerDashboard/profilerDashboard.interface';
import { TableProfilerChartProps } from '../TableProfiler.interface'; import { TableProfilerChartProps } from '../TableProfiler.interface';
const TableProfilerChart = ({ selectedTimeRange }: TableProfilerChartProps) => { const TableProfilerChart = ({ dateRangeObject }: TableProfilerChartProps) => {
const { datasetFQN } = useParams<{ datasetFQN: string }>(); const { datasetFQN } = useParams<{ datasetFQN: string }>();
const [rowCountMetrics, setRowCountMetrics] = useState<MetricChartType>( const [rowCountMetrics, setRowCountMetrics] = useState<MetricChartType>(
@ -53,28 +47,24 @@ const TableProfilerChart = ({ selectedTimeRange }: TableProfilerChartProps) => {
useState<MetricChartType>(INITIAL_OPERATION_METRIC_VALUE); useState<MetricChartType>(INITIAL_OPERATION_METRIC_VALUE);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const fetchTableProfiler = async (fqn: string, days = 3) => { const fetchTableProfiler = async (
fqn: string,
dateRangeObj: DateRangeObject
) => {
try { try {
const startTs = getPastDatesTimeStampFromCurrentDate(days); const { data } = await getTableProfilesList(fqn, dateRangeObj);
const endTs = getCurrentDateTimeStamp();
const { data } = await getTableProfilesList(fqn, {
startTs,
endTs,
});
const rowMetricsData = calculateRowCountMetrics(data, rowCountMetrics); const rowMetricsData = calculateRowCountMetrics(data, rowCountMetrics);
setRowCountMetrics(rowMetricsData); setRowCountMetrics(rowMetricsData);
} catch (error) { } catch (error) {
showErrorToast(error as AxiosError); showErrorToast(error as AxiosError);
} }
}; };
const fetchSystemProfiler = async (fqn: string, days = 3) => { const fetchSystemProfiler = async (
fqn: string,
dateRangeObj: DateRangeObject
) => {
try { try {
const startTs = getPastDaysDateTimeMillis(days); const { data } = await getSystemProfileList(fqn, dateRangeObj);
const endTs = getCurrentDateTimeMillis();
const { data } = await getSystemProfileList(fqn, {
startTs,
endTs,
});
const { operationMetrics: metricsData, operationDateMetrics } = const { operationMetrics: metricsData, operationDateMetrics } =
calculateSystemMetrics(data, operationMetrics); 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); setIsLoading(true);
await fetchTableProfiler(fqn, days); await fetchTableProfiler(fqn, dateRangeObj);
await fetchSystemProfiler(fqn, days); await fetchSystemProfiler(fqn, {
startTs: dateRangeObj.startTs * 1000,
endTs: dateRangeObj.endTs * 1000,
});
setIsLoading(false); setIsLoading(false);
}; };
useEffect(() => { useEffect(() => {
if (datasetFQN) { if (datasetFQN) {
fetchProfilerData( fetchProfilerData(datasetFQN, dateRangeObject);
datasetFQN,
PROFILER_FILTER_RANGE[selectedTimeRange].days
);
} else { } else {
setIsLoading(false); setIsLoading(false);
} }
}, [datasetFQN, selectedTimeRange]); }, [datasetFQN, dateRangeObject]);
if (isLoading) { if (isLoading) {
return <Loader />; return <Loader />;

View File

@ -11,7 +11,7 @@
* limitations under the License. * 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 { SystemProfile } from '../../generated/api/data/createTableProfile';
import { import {
Column, Column,
@ -82,7 +82,7 @@ export type TableProfilerData = {
}; };
export type TableProfilerChartProps = { export type TableProfilerChartProps = {
selectedTimeRange: keyof typeof PROFILER_FILTER_RANGE; dateRangeObject: DateRangeObject;
}; };
export interface ProfilerSettingModalState { export interface ProfilerSettingModalState {

View File

@ -28,7 +28,9 @@ import { DefaultOptionType } from 'antd/lib/select';
import { SwitchChangeEventHandler } from 'antd/lib/switch'; import { SwitchChangeEventHandler } from 'antd/lib/switch';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import classNames from 'classnames'; 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 React, { FC, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Link, useParams } from 'react-router-dom'; 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 { API_RES_MAX_SIZE } from '../../constants/constants';
import { PAGE_HEADERS } from '../../constants/PageHeaders.constant'; import { PAGE_HEADERS } from '../../constants/PageHeaders.constant';
import { import {
DEFAULT_RANGE_DATA,
INITIAL_TEST_RESULT_SUMMARY, INITIAL_TEST_RESULT_SUMMARY,
PROFILER_FILTER_RANGE,
} from '../../constants/profiler.constant'; } from '../../constants/profiler.constant';
import { ProfilerDashboardType } from '../../enums/table.enum'; import { ProfilerDashboardType } from '../../enums/table.enum';
import { ProfileSampleType, Table } from '../../generated/entity/data/table'; import { ProfileSampleType, Table } from '../../generated/entity/data/table';
@ -95,8 +97,8 @@ const TableProfilerV1: FC<TableProfilerProps> = ({
const [selectedTestType, setSelectedTestType] = useState(''); const [selectedTestType, setSelectedTestType] = useState('');
const [deleted, setDeleted] = useState<boolean>(false); const [deleted, setDeleted] = useState<boolean>(false);
const [isTestCaseLoading, setIsTestCaseLoading] = useState(false); const [isTestCaseLoading, setIsTestCaseLoading] = useState(false);
const [selectedTimeRange, setSelectedTimeRange] = const [dateRangeObject, setDateRangeObject] =
useState<keyof typeof PROFILER_FILTER_RANGE>('last3days'); useState<DateRangeObject>(DEFAULT_RANGE_DATA);
const isSummary = activeTab === ProfilerDashboardTab.SUMMARY; const isSummary = activeTab === ProfilerDashboardTab.SUMMARY;
const isDataQuality = activeTab === ProfilerDashboardTab.DATA_QUALITY; const isDataQuality = activeTab === ProfilerDashboardTab.DATA_QUALITY;
const isProfiler = activeTab === ProfilerDashboardTab.PROFILER; 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) => { const handleTabChange: MenuProps['onClick'] = (value) => {
setActiveTab(value.key as ProfilerDashboardTab); setActiveTab(value.key as ProfilerDashboardTab);
}; };
const handleTimeRangeChange = (value: keyof typeof PROFILER_FILTER_RANGE) => { const handleDateRangeChange = (value: DateRangeObject) => {
if (value !== selectedTimeRange) { if (!isEqual(value, dateRangeObject)) {
setSelectedTimeRange(value); setDateRangeObject(value);
} }
}; };
@ -400,11 +395,9 @@ const TableProfilerV1: FC<TableProfilerProps> = ({
)} )}
{isProfiler && ( {isProfiler && (
<Select <DatePickerMenu
className="tw-w-32" showSelectedCustomRange
options={timeRangeOption} handleDateRangeChange={handleDateRangeChange}
value={selectedTimeRange}
onChange={handleTimeRangeChange}
/> />
)} )}
@ -515,7 +508,7 @@ const TableProfilerV1: FC<TableProfilerProps> = ({
)} )}
{isProfiler && ( {isProfiler && (
<TableProfilerChart selectedTimeRange={selectedTimeRange} /> <TableProfilerChart dateRangeObject={dateRangeObject} />
)} )}
{settingModalVisible && ( {settingModalVisible && (

View File

@ -52,7 +52,7 @@ export const DATA_INSIGHT_GRAPH_COLORS = [
]; ];
export const BAR_SIZE = 15; export const BAR_SIZE = 15;
export const DEFAULT_DAYS = 30; export const DEFAULT_DAYS = 1;
export const ENTITIES_BAR_COLO_MAP: Record<string, string> = { export const ENTITIES_BAR_COLO_MAP: Record<string, string> = {
Chart: '#E7B85D', Chart: '#E7B85D',

View File

@ -14,6 +14,10 @@
import { t } from 'i18next'; import { t } from 'i18next';
import { StepperStepType } from 'Models'; import { StepperStepType } from 'Models';
import i18n from 'utils/i18next/LocalUtil'; import i18n from 'utils/i18next/LocalUtil';
import {
getCurrentDateTimeStamp,
getPastDatesTimeStampFromCurrentDate,
} from 'utils/TimeUtils';
import { CSMode } from '../enums/codemirror.enum'; import { CSMode } from '../enums/codemirror.enum';
import { DMLOperationType } from '../generated/api/data/createTableProfile'; import { DMLOperationType } from '../generated/api/data/createTableProfile';
import { import {
@ -67,6 +71,10 @@ export const PROFILER_METRIC = [
]; ];
export const PROFILER_FILTER_RANGE = { export const PROFILER_FILTER_RANGE = {
yesterday: {
days: 1,
title: t('label.yesterday'),
},
last3days: { last3days: {
days: 3, days: 3,
title: t('label.last-number-of-days', { 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 COLORS = ['#7147E8', '#B02AAC', '#B02AAC', '#1890FF', '#008376'];
export const DEFAULT_CHART_COLLECTION_VALUE = { export const DEFAULT_CHART_COLLECTION_VALUE = {

View File

@ -153,6 +153,7 @@
"custom-oidc": "CustomOidc", "custom-oidc": "CustomOidc",
"custom-property": "Custom property", "custom-property": "Custom property",
"custom-property-plural": "Custom Properties", "custom-property-plural": "Custom Properties",
"custom-range": "Custom Range",
"dag": "Dag", "dag": "Dag",
"dag-view": "DAG view", "dag-view": "DAG view",
"daily-active-users-on-the-platform": "Daily Active Users on the Platform", "daily-active-users-on-the-platform": "Daily Active Users on the Platform",
@ -910,6 +911,7 @@
"weekly-usage": "Weekly Usage", "weekly-usage": "Weekly Usage",
"whats-new": "What's New", "whats-new": "What's New",
"yes": "Yes", "yes": "Yes",
"yesterday": "Yesterday",
"your-entity": "Your {{entity}}" "your-entity": "Your {{entity}}"
}, },
"message": { "message": {

View File

@ -153,6 +153,7 @@
"custom-oidc": "OIDC personalizado", "custom-oidc": "OIDC personalizado",
"custom-property": "Propiedad personalizada", "custom-property": "Propiedad personalizada",
"custom-property-plural": "Propiedades personalizadas", "custom-property-plural": "Propiedades personalizadas",
"custom-range": "Custom Range",
"dag": "DAG", "dag": "DAG",
"dag-view": "Vista DAG", "dag-view": "Vista DAG",
"daily-active-users-on-the-platform": "Usuarios activos diarios en la plataforma", "daily-active-users-on-the-platform": "Usuarios activos diarios en la plataforma",
@ -910,6 +911,7 @@
"weekly-usage": "Uso semanal", "weekly-usage": "Uso semanal",
"whats-new": "Novedades", "whats-new": "Novedades",
"yes": "Sí", "yes": "Sí",
"yesterday": "Yesterday",
"your-entity": "Tu {{entity}}" "your-entity": "Tu {{entity}}"
}, },
"message": { "message": {

View File

@ -153,6 +153,7 @@
"custom-oidc": "CustomOidc", "custom-oidc": "CustomOidc",
"custom-property": "Custom property", "custom-property": "Custom property",
"custom-property-plural": "Propriétés Personalisées", "custom-property-plural": "Propriétés Personalisées",
"custom-range": "Custom Range",
"dag": "Dag", "dag": "Dag",
"dag-view": "Vue DAG", "dag-view": "Vue DAG",
"daily-active-users-on-the-platform": "Utilisateurs actifs quotidiens", "daily-active-users-on-the-platform": "Utilisateurs actifs quotidiens",
@ -910,6 +911,7 @@
"weekly-usage": "Weekly Usage", "weekly-usage": "Weekly Usage",
"whats-new": "Nouveau", "whats-new": "Nouveau",
"yes": "Yes", "yes": "Yes",
"yesterday": "Yesterday",
"your-entity": "Your {{entity}}" "your-entity": "Your {{entity}}"
}, },
"message": { "message": {

View File

@ -153,6 +153,7 @@
"custom-oidc": "CustomOidc", "custom-oidc": "CustomOidc",
"custom-property": "カスタムプロパティ", "custom-property": "カスタムプロパティ",
"custom-property-plural": "カスタムプロパティ", "custom-property-plural": "カスタムプロパティ",
"custom-range": "Custom Range",
"dag": "Dag", "dag": "Dag",
"dag-view": "DAGビュー", "dag-view": "DAGビュー",
"daily-active-users-on-the-platform": "このプラットフォームのアクティブなユーザー", "daily-active-users-on-the-platform": "このプラットフォームのアクティブなユーザー",
@ -910,6 +911,7 @@
"weekly-usage": "Weekly Usage", "weekly-usage": "Weekly Usage",
"whats-new": "最新情報", "whats-new": "最新情報",
"yes": "はい", "yes": "はい",
"yesterday": "Yesterday",
"your-entity": "あなたの{{entity}}" "your-entity": "あなたの{{entity}}"
}, },
"message": { "message": {

View File

@ -153,6 +153,7 @@
"custom-oidc": "OIDC customizado", "custom-oidc": "OIDC customizado",
"custom-property": "Propriedade customizada", "custom-property": "Propriedade customizada",
"custom-property-plural": "Propriedades customizadas", "custom-property-plural": "Propriedades customizadas",
"custom-range": "Custom Range",
"dag": "DAG", "dag": "DAG",
"dag-view": "Visão da DAG", "dag-view": "Visão da DAG",
"daily-active-users-on-the-platform": "Usuários ativos diariamente na plataforma", "daily-active-users-on-the-platform": "Usuários ativos diariamente na plataforma",
@ -910,6 +911,7 @@
"weekly-usage": "Uso semanal", "weekly-usage": "Uso semanal",
"whats-new": "O que há de novo", "whats-new": "O que há de novo",
"yes": "Sim", "yes": "Sim",
"yesterday": "Yesterday",
"your-entity": "Sua {{entity}}" "your-entity": "Sua {{entity}}"
}, },
"message": { "message": {

View File

@ -153,6 +153,7 @@
"custom-oidc": "CustomOidc", "custom-oidc": "CustomOidc",
"custom-property": "Custom property", "custom-property": "Custom property",
"custom-property-plural": "定制属性", "custom-property-plural": "定制属性",
"custom-range": "Custom Range",
"dag": "Dag", "dag": "Dag",
"dag-view": "DAG view", "dag-view": "DAG view",
"daily-active-users-on-the-platform": "Daily active users on the platform", "daily-active-users-on-the-platform": "Daily active users on the platform",
@ -910,6 +911,7 @@
"weekly-usage": "Weekly Usage", "weekly-usage": "Weekly Usage",
"whats-new": "What's new", "whats-new": "What's new",
"yes": "是", "yes": "是",
"yesterday": "Yesterday",
"your-entity": "Your {{entity}}" "your-entity": "Your {{entity}}"
}, },
"message": { "message": {

View File

@ -11,16 +11,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { import { Button, Card, Col, Row, Space, Tooltip, Typography } from 'antd';
Button,
Card,
Col,
Row,
Select,
Space,
Tooltip,
Typography,
} from 'antd';
import PageContainerV1 from 'components/containers/PageContainerV1'; import PageContainerV1 from 'components/containers/PageContainerV1';
import PageLayoutV1 from 'components/containers/PageLayoutV1'; import PageLayoutV1 from 'components/containers/PageLayoutV1';
import DailyActiveUsersChart from 'components/DataInsightDetail/DailyActiveUsersChart'; import DailyActiveUsersChart from 'components/DataInsightDetail/DailyActiveUsersChart';
@ -33,11 +24,14 @@ 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';
import TotalEntityInsight from 'components/DataInsightDetail/TotalEntityInsight'; 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 SearchDropdown from 'components/SearchDropdown/SearchDropdown';
import { SearchDropdownOption } from 'components/SearchDropdown/SearchDropdown.interface'; import { SearchDropdownOption } from 'components/SearchDropdown/SearchDropdown.interface';
import { DEFAULT_RANGE_DATA } from 'constants/profiler.constant';
import { EntityFields } from 'enums/AdvancedSearch.enum'; import { EntityFields } from 'enums/AdvancedSearch.enum';
import { t } from 'i18next'; import { t } from 'i18next';
import { isEmpty } from 'lodash'; import { isEmpty, isEqual } from 'lodash';
import React, { useEffect, useLayoutEffect, useMemo, useState } from 'react'; import React, { useEffect, useLayoutEffect, useMemo, useState } from 'react';
import { ListItem } from 'react-awesome-query-builder'; import { ListItem } from 'react-awesome-query-builder';
import { useHistory, useParams } from 'react-router-dom'; import { useHistory, useParams } from 'react-router-dom';
@ -46,7 +40,6 @@ import { searchQuery } from 'rest/searchAPI';
import { autocomplete } from '../../constants/AdvancedSearch.constants'; import { autocomplete } from '../../constants/AdvancedSearch.constants';
import { PAGE_SIZE, ROUTES } from '../../constants/constants'; import { PAGE_SIZE, ROUTES } from '../../constants/constants';
import { import {
DAY_FILTER,
DEFAULT_DAYS, DEFAULT_DAYS,
ENTITIES_CHARTS, ENTITIES_CHARTS,
INITIAL_CHART_FILTER, INITIAL_CHART_FILTER,
@ -64,11 +57,7 @@ import {
getDataInsightPathWithFqn, getDataInsightPathWithFqn,
getTeamFilter, getTeamFilter,
} from '../../utils/DataInsightUtils'; } from '../../utils/DataInsightUtils';
import { import { getFormattedDateFromMilliSeconds } from '../../utils/TimeUtils';
getCurrentDateTimeMillis,
getFormattedDateFromMilliSeconds,
getPastDaysDateTimeMillis,
} from '../../utils/TimeUtils';
import { TeamStateType, TierStateType } from './DataInsight.interface'; import { TeamStateType, TierStateType } from './DataInsight.interface';
import './DataInsight.less'; import './DataInsight.less';
import DataInsightLeftPanel from './DataInsightLeftPanel'; import DataInsightLeftPanel from './DataInsightLeftPanel';
@ -101,6 +90,8 @@ const DataInsightPage = () => {
useState<ChartFilter>(INITIAL_CHART_FILTER); useState<ChartFilter>(INITIAL_CHART_FILTER);
const [kpiList, setKpiList] = useState<Array<Kpi>>([]); const [kpiList, setKpiList] = useState<Array<Kpi>>([]);
const [selectedDaysFilter, setSelectedDaysFilter] = useState(DEFAULT_DAYS); const [selectedDaysFilter, setSelectedDaysFilter] = useState(DEFAULT_DAYS);
const [dateRangeObject, setDateRangeObject] =
useState<DateRangeObject>(DEFAULT_RANGE_DATA);
const [selectedChart, setSelectedChart] = useState<DataInsightChartType>(); const [selectedChart, setSelectedChart] = useState<DataInsightChartType>();
@ -133,13 +124,20 @@ const DataInsightPage = () => {
})); }));
}; };
const handleDaysChange = (days: number) => { const handleDateRangeChange = (
setSelectedDaysFilter(days); value: DateRangeObject,
setChartFilter((previous) => ({ daysValue?: number
...previous, ) => {
startTs: getPastDaysDateTimeMillis(days), if (!isEqual(value, dateRangeObject)) {
endTs: getCurrentDateTimeMillis(), 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[] = []) => { const handleTeamChange = (teams: SearchDropdownOption[] = []) => {
@ -341,11 +339,9 @@ const DataInsightPage = () => {
'dd MMM yyyy' 'dd MMM yyyy'
)}`} )}`}
</Typography> </Typography>
<Select <DatePickerMenu
className="data-insight-select-dropdown" handleDateRangeChange={handleDateRangeChange}
defaultValue={DEFAULT_DAYS} showSelectedCustomRange={false}
options={DAY_FILTER}
onChange={handleDaysChange}
/> />
</Space> </Space>
</Space> </Space>

View File

@ -20,8 +20,10 @@ import {
OperationPermission, OperationPermission,
ResourceEntity, ResourceEntity,
} from 'components/PermissionProvider/PermissionProvider.interface'; } from 'components/PermissionProvider/PermissionProvider.interface';
import { DateRangeObject } from 'components/ProfilerDashboard/component/TestSummary';
import ProfilerDashboard from 'components/ProfilerDashboard/ProfilerDashboard'; import ProfilerDashboard from 'components/ProfilerDashboard/ProfilerDashboard';
import { ProfilerDashboardTab } from 'components/ProfilerDashboard/profilerDashboard.interface'; import { ProfilerDashboardTab } from 'components/ProfilerDashboard/profilerDashboard.interface';
import { DEFAULT_RANGE_DATA } from 'constants/profiler.constant';
import { compare } from 'fast-json-patch'; import { compare } from 'fast-json-patch';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
@ -46,10 +48,6 @@ import {
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils'; import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
import { getDecodedFqn } from '../../utils/StringsUtils'; import { getDecodedFqn } from '../../utils/StringsUtils';
import { generateEntityLink } from '../../utils/TableUtils'; import { generateEntityLink } from '../../utils/TableUtils';
import {
getCurrentDateTimeStamp,
getPastDatesTimeStampFromCurrentDate,
} from '../../utils/TimeUtils';
import { showErrorToast } from '../../utils/ToastUtils'; import { showErrorToast } from '../../utils/ToastUtils';
const ProfilerDashboardPage = () => { const ProfilerDashboardPage = () => {
@ -89,16 +87,15 @@ const ProfilerDashboardPage = () => {
} }
}; };
const fetchProfilerData = async (fqn: string, days = 3) => { const fetchProfilerData = async (
fqn: string,
dateRangeObject?: DateRangeObject
) => {
try { try {
const startTs = getPastDatesTimeStampFromCurrentDate(days); const { data } = await getColumnProfilerList(
fqn,
const endTs = getCurrentDateTimeStamp(); dateRangeObject ?? DEFAULT_RANGE_DATA
);
const { data } = await getColumnProfilerList(fqn, {
startTs,
endTs,
});
setProfilerData(data); setProfilerData(data);
} catch (error) { } catch (error) {
showErrorToast(error as AxiosError); showErrorToast(error as AxiosError);

View File

@ -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;
};