mirror of
				https://github.com/open-metadata/OpenMetadata.git
				synced 2025-10-31 10:39:30 +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.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(); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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 | ||||||
|  | |||||||
| @ -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> | ||||||
|  | |||||||
| @ -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; | ||||||
| } | } | ||||||
|  | |||||||
| @ -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
 | ||||||
|  | |||||||
| @ -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 />; | ||||||
|  | |||||||
| @ -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 { | ||||||
|  | |||||||
| @ -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 && ( | ||||||
|  | |||||||
| @ -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', | ||||||
|  | |||||||
| @ -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 = { | ||||||
|  | |||||||
| @ -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": { | ||||||
|  | |||||||
| @ -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": { | ||||||
|  | |||||||
| @ -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": { | ||||||
|  | |||||||
| @ -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": { | ||||||
|  | |||||||
| @ -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": { | ||||||
|  | |||||||
| @ -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": { | ||||||
|  | |||||||
| @ -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, | ||||||
|  |     daysValue?: number | ||||||
|  |   ) => { | ||||||
|  |     if (!isEqual(value, dateRangeObject)) { | ||||||
|  |       setDateRangeObject(value); | ||||||
|  |       setSelectedDaysFilter(daysValue ?? 0); | ||||||
|       setChartFilter((previous) => ({ |       setChartFilter((previous) => ({ | ||||||
|         ...previous, |         ...previous, | ||||||
|       startTs: getPastDaysDateTimeMillis(days), |         // Converting coming data to milliseconds
 | ||||||
|       endTs: getCurrentDateTimeMillis(), |         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> | ||||||
|  | |||||||
| @ -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); | ||||||
|  | |||||||
| @ -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
	 Aniket Katkar
						Aniket Katkar