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