This is an automated email from the ASF dual-hosted git repository.
adoroszlai pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/master by this push:
new 0e0d5e9bad HDDS-11163. Improve Heatmap page UI (#7420)
0e0d5e9bad is described below
commit 0e0d5e9bad3b2acfb1cbac260b47755cbdcd1c7f
Author: Abhishek Pal <[email protected]>
AuthorDate: Tue Nov 19 17:21:54 2024 +0530
HDDS-11163. Improve Heatmap page UI (#7420)
---
.../src/v2/components/navBar/navBar.tsx | 2 +-
.../src/v2/components/plots/heatmapPlot.tsx | 151 ++++++++
.../src/v2/constants/heatmap.constants.tsx | 47 +++
.../src/v2/pages/heatmap/heatmap.less | 86 +++++
.../src/v2/pages/heatmap/heatmap.tsx | 388 +++++++++++++++++++++
.../recon/ozone-recon-web/src/v2/routes-v2.tsx | 5 +-
.../ozone-recon-web/src/v2/types/heatmap.types.ts | 51 +++
7 files changed, 725 insertions(+), 5 deletions(-)
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/navBar/navBar.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/navBar/navBar.tsx
index 3da4104634..1dd1ede48d 100644
---
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/navBar/navBar.tsx
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/navBar/navBar.tsx
@@ -144,7 +144,7 @@ const NavBar: React.FC<NavBarProps> = ({
<span>Heatmap</span>
<Link to={{
pathname: '/Heatmap',
- state: { isHeatmapEnabled: true }
+ state: { isHeatmapEnabled: isHeatmapEnabled }
}}
/>
</Menu.Item>
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/plots/heatmapPlot.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/plots/heatmapPlot.tsx
new file mode 100644
index 0000000000..a58a7704da
--- /dev/null
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/plots/heatmapPlot.tsx
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 React from 'react';
+import { AgChartsReact } from 'ag-charts-react';
+import { byteToSize } from '@/utils/common';
+import { HeatmapResponse } from '@/v2/types/heatmap.types';
+
+type HeatmapPlotProps = {
+ data: HeatmapResponse;
+ onClick: (arg0: string) => void;
+ colorScheme: string[];
+ entityType: string;
+};
+
+const capitalize = <T extends string>(str: T) => {
+ return str.charAt(0).toUpperCase() + str.slice(1) as Capitalize<typeof str>;
+}
+
+const HeatmapPlot: React.FC<HeatmapPlotProps> = ({
+ data,
+ onClick,
+ colorScheme,
+ entityType = ''
+}) => {
+
+ const tooltipContent = (params: any) => {
+ let tooltipContent = `<span>
+ <strong>Size</strong>:
+ ${byteToSize(params.datum.size, 1)}
+ `;
+ if (params.datum.accessCount !== undefined) {
+ tooltipContent += `<br/>
+ <strong>Access count</strong>:
+ ${params.datum.accessCount }
+ `;
+ }
+ else{
+ tooltipContent += `<br/>
+ <strong>Max Access Count</strong>:
+ ${params.datum.maxAccessCount}
+ `;}
+ if (params.datum.label !== '') {
+ tooltipContent += `<br/>
+ <strong>Entity Name</strong>:
+ ${params.datum.label ? params.datum.label.split('/').slice(-1) : ''}
+ `;
+ }
+ tooltipContent += '</span>';
+ return tooltipContent;
+ };
+
+ const heatmapConfig = {
+ type: 'treemap',
+ labelKey: 'label',// the name of the key to fetch the label value from
+ sizeKey: 'normalizedSize',// the name of the key to fetch the value that
will determine tile size
+ colorKey: 'color',
+ title: { color: '#424242', fontSize: 14, fontFamily: 'Roboto', fontWeight:
'600' },
+ subtitle: { color: '#424242', fontSize: 12, fontFamily: 'Roboto',
fontWeight: '400' },
+ tooltip: {
+ renderer: (params) => {
+ return {
+ content: tooltipContent(params)
+ };
+ }
+ },
+ formatter: ({ highlighted }: { highlighted: boolean }) => {
+ const stroke = highlighted ? '#CED4D9' : '#FFFFFF';
+ return { stroke };
+ },
+ labels: {
+ color: '#FFFFFF',
+ fontWeight: 'bold',
+ fontSize: 12
+ },
+ tileStroke: '#FFFFFF',
+ tileStrokeWidth: 1.4,
+ colorDomain: [
+ 0.000,
+ 0.050,
+ 0.100,
+ 0.150,
+ 0.200,
+ 0.250,
+ 0.300,
+ 0.350,
+ 0.400,
+ 0.450,
+ 0.500,
+ 0.550,
+ 0.600,
+ 0.650,
+ 0.700,
+ 0.750,
+ 0.800,
+ 0.850,
+ 0.900,
+ 0.950,
+ 1.000
+ ],
+ colorRange: [...colorScheme],
+ groupFill: '#E6E6E6',
+ groupStroke: '#E1E2E6',
+ nodePadding: 3,
+ labelShadow: { enabled: false }, //labels shadow
+ gradient: false,
+ highlightStyle: {
+ text: {
+ color: '#424242',
+ },
+ item: {
+ fill: 'rgba(0, 0 ,0, 0.0)',
+ },
+ },
+ listeners: {
+ nodeClick: (event) => {
+ var data = event.datum;
+ // Leaf level box should not call API
+ if (!data.color)
+ if (data.path) {
+ onClick(data.path);
+ }
+ },
+ },
+ }
+
+ const options = {
+ data,
+ series: [heatmapConfig],
+ title: { text: `${capitalize(entityType)} Heatmap`}
+ };
+
+ return <AgChartsReact options={options} />
+}
+
+export default HeatmapPlot;
\ No newline at end of file
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/constants/heatmap.constants.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/constants/heatmap.constants.tsx
new file mode 100644
index 0000000000..63a8476648
--- /dev/null
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/constants/heatmap.constants.tsx
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+export const colourScheme = {
+ amberAlert: [
+ '#FFCF88',
+ '#FFCA87',
+ '#FFC586',
+ '#FFC085',
+ '#FFBB83',
+ '#FFB682',
+ '#FFB181',
+ '#FFA676',
+ '#FF9F6F',
+ '#FF9869',
+ '#FF9262',
+ '#FF8B5B',
+ '#FF8455',
+ '#FF7D4E',
+ '#FF8282',
+ '#FF7776',
+ '#FF6D6A',
+ '#FF625F',
+ '#FF5753',
+ '#FF4D47',
+ '#FF423B'
+ ]
+};
+
+export const TIME_PERIODS: string[] = ['24H', '7D', '90D']
+export const ENTITY_TYPES: string[] = ['key', 'bucket', 'volume']
+export const ROOT_PATH = '/'
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/heatmap/heatmap.less
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/heatmap/heatmap.less
new file mode 100644
index 0000000000..57eaf8391d
--- /dev/null
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/heatmap/heatmap.less
@@ -0,0 +1,86 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you 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.
+*/
+
+.content-div {
+ min-height: unset;
+ padding-bottom: 10px;
+
+ .heatmap-header-section {
+ display: flex;
+ justify-content: space-between;
+ align-items: stretch;
+
+ @media (max-width: 1680px) {
+ flex-direction: column;
+ }
+
+ .heatmap-filter-section {
+ font-size: 14px;
+ font-weight: normal;
+ display: flex;
+ column-gap: 12px;
+
+ .path-input-container {
+ display: inline-flex;
+ align-items: flex-start;
+
+ .path-input-element {
+ margin: 0px 0px 0px 10px;
+ width: 20vw;
+ }
+ }
+
+ .entity-dropdown-button {
+ display: inline-block;
+ }
+
+ .date-dropdown-button {
+ display: inline-block;
+ }
+
+ .input-bar {
+ display: inline-block;
+ margin-left: 25px;
+ }
+
+ .input {
+ padding-left: 5px;
+ margin-right: 10px;
+ display: inline-block;
+ width: 400px;
+ }
+ }
+
+ .heatmap-legend-container {
+ display: flex !important;
+ align-self: flex-start;
+
+ .heatmap-legend-item {
+ display: flex;
+ align-items: center;
+ margin-left: 20px;
+ }
+ }
+ }
+
+ #heatmap-plot-container {
+ height: 600px;
+ width: 84vw;
+ margin-left: -12px;
+ }
+}
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/heatmap/heatmap.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/heatmap/heatmap.tsx
new file mode 100644
index 0000000000..5cf2d64bd5
--- /dev/null
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/pages/heatmap/heatmap.tsx
@@ -0,0 +1,388 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 React, { ChangeEvent, useRef, useState } from 'react';
+import moment, { Moment } from 'moment';
+import { Row, Button, Menu, Input, Dropdown, DatePicker, Form, Result,
message, Spin } from 'antd';
+import { MenuProps } from 'antd/es/menu';
+import { DownOutlined, LoadingOutlined, UndoOutlined } from
'@ant-design/icons';
+
+
+import { showDataFetchError } from '@/utils/common';
+import { AxiosGetHelper } from '@/utils/axiosRequestHelper';
+import * as CONSTANTS from '@/v2/constants/heatmap.constants';
+import { HeatmapChild, HeatmapResponse, HeatmapState, InputPathState,
InputPathValidTypes, IResponseError } from '@/v2/types/heatmap.types';
+import HeatmapPlot from '@/v2/components/plots/heatmapPlot';
+
+import './heatmap.less';
+import { useLocation } from 'react-router-dom';
+import { AxiosResponse } from 'axios';
+
+let minSize = Infinity;
+let maxSize = 0;
+
+const Heatmap: React.FC<{}> = () => {
+
+ const [state, setState] = useState<HeatmapState>({
+ heatmapResponse: {
+ label: '',
+ path: '',
+ children: [],
+ size: 0,
+ maxAccessCount: 0,
+ minAccessCount: 0
+ },
+ entityType: CONSTANTS.ENTITY_TYPES[0],
+ date: CONSTANTS.TIME_PERIODS[0]
+ });
+
+ const [inputPathState, setInputPathState] = useState<InputPathState>({
+ inputPath: CONSTANTS.ROOT_PATH,
+ isInputPathValid: undefined,
+ helpMessage: ''
+ });
+
+ const [isLoading, setLoading] = useState<boolean>(false);
+ const [treeEndpointFailed, setTreeEndpointFailed] = useState<boolean>(false);
+
+ const location = useLocation();
+ const cancelSignal = useRef<AbortController>();
+ const cancelDisabledFeatureSignal = useRef<AbortController>();
+
+ const [isHeatmapEnabled, setIsHeatmapEnabled] =
useState<boolean>(location?.state?.isHeatmapEnabled);
+
+
+ function handleChange(e: ChangeEvent<HTMLInputElement>) {
+ const value = e.target.value;
+ // Only allow letters, numbers,underscores and forward slashes and hyphen
+ const regex = /^[a-zA-Z0-9_/-]*$/;
+
+ let inputValid = undefined;
+ let helpMessage = '';
+ if (!regex.test(value)) {
+ helpMessage = 'Please enter valid path';
+ inputValid = 'error';
+ }
+ setInputPathState({
+ inputPath: value,
+ isInputPathValid: inputValid as InputPathValidTypes,
+ helpMessage: helpMessage
+ });
+ }
+
+ function handleSubmit() {
+ updateHeatmap(inputPathState.inputPath, state.entityType, state.date);
+ }
+
+ const normalize = (min: number, max: number, size: number) => {
+ // Since there can be a huge difference between the largest entity size
+ // and the smallest entity size, it might cause some blocks to render
smaller
+ // we are normalizing the size to ensure all entities are visible
+ //Normaized Size using Deviation and mid Point
+ const mean = (max + min) / 2;
+ const highMean = (max + mean) / 2;
+ const lowMean1 = (min + mean) / 2;
+ const lowMean2 = (lowMean1 + min) / 2;
+
+ if (size > highMean) {
+ const newsize = highMean + (size * 0.1);
+ return (newsize);
+ }
+ if (size < lowMean2) {
+ const diff = (lowMean2 - size) / 2;
+ const newSize = lowMean2 - diff;
+ return (newSize);
+ }
+
+ return size;
+ };
+
+ const updateSize = (obj: HeatmapResponse | HeatmapChild) => {
+ //Normalize Size so other blocks also get visualized if size is large in
bytes minimize and if size is too small make it big
+ // it will only apply on leaf level as checking color property
+ if (obj.hasOwnProperty('size') && obj.hasOwnProperty('color')) {
+
+ // hide block at key,volume,bucket level if size accessCount and
maxAccessCount are zero apply normalized size only for leaf level
+ if ((obj as HeatmapChild)?.size === 0 && (obj as
HeatmapChild)?.accessCount === 0) {
+ obj['normalizedSize'] = 0;
+ } else if ((obj as HeatmapResponse)?.size === 0 && (obj as
HeatmapResponse)?.maxAccessCount === 0) {
+ obj['normalizedSize'] = 0;
+ }
+ else if (obj?.size === 0 && ((obj as HeatmapChild)?.accessCount >= 0 ||
(obj as HeatmapResponse).maxAccessCount >= 0)) {
+ obj['normalizedSize'] = 1;
+ obj.size = 0;
+ }
+ else {
+ const newSize = normalize(minSize, maxSize, obj.size);
+ obj['normalizedSize'] = newSize;
+ }
+ }
+
+ if (obj.hasOwnProperty('children')) {
+ (obj as HeatmapResponse)?.children.forEach(child => updateSize(child));
+ }
+ return obj as HeatmapResponse;
+ };
+
+ const updateHeatmap = (path: string, entityType: string, date: string |
number) => {
+ // Only perform requests if the heatmap is enabled
+ if (isHeatmapEnabled) {
+ setLoading(true);
+ // We want to ensure these are not empty as they will be passed as path
params
+ if (date && path && entityType) {
+ const { request, controller } = AxiosGetHelper(
+
`/api/v1/heatmap/readaccess?startDate=${date}&path=${path}&entityType=${entityType}`,
+ cancelSignal.current
+ );
+ cancelSignal.current = controller;
+
+ request.then(response => {
+ if (response?.status === 200) {
+ minSize = response.data.minAccessCount;
+ maxSize = response.data.maxAccessCount;
+ const heatmapResponse: HeatmapResponse = updateSize(response.data);
+ setLoading(false);
+ setState(prevState => ({
+ ...prevState,
+ heatmapResponse: heatmapResponse
+ }));
+ } else {
+ const error = new Error((response.status).toString()) as
IResponseError;
+ error.status = response.status;
+ error.message = `Failed to fetch Heatmap Response with status
${error.status}`
+ throw error;
+ }
+ }).catch(error => {
+ setLoading(false);
+ setInputPathState(prevState => ({
+ ...prevState,
+ inputPath: CONSTANTS.ROOT_PATH
+ }));
+ setTreeEndpointFailed(true);
+ if (error.response.status !== 404) {
+ showDataFetchError(error.message.toString());
+ }
+ });
+ } else {
+ setLoading(false);
+ }
+
+ }
+ }
+
+ const updateHeatmapParent = (path: string) => {
+ setInputPathState(prevState => ({
+ ...prevState,
+ inputPath: path
+ }));
+ }
+
+ function isDateDisabled(current: Moment) {
+ return current > moment() || current < moment().subtract(90, 'day');
+ }
+
+ function getIsHeatmapEnabled() {
+ const disabledfeaturesEndpoint = `/api/v1/features/disabledFeatures`;
+ const { request, controller } = AxiosGetHelper(
+ disabledfeaturesEndpoint,
+ cancelDisabledFeatureSignal.current
+ )
+ cancelDisabledFeatureSignal.current = controller;
+ request.then(response => {
+ setIsHeatmapEnabled(!response?.data?.includes('HEATMAP'));
+ }).catch(error => {
+ showDataFetchError((error as Error).toString());
+ });
+ }
+
+ React.useEffect(() => {
+ // We do not know if heatmap is enabled or not, so set it
+ if (isHeatmapEnabled === undefined) {
+ getIsHeatmapEnabled();
+ }
+ updateHeatmap(inputPathState.inputPath, state.entityType, state.date);
+
+ return (() => {
+ cancelSignal.current && cancelSignal.current.abort();
+ })
+ }, [isHeatmapEnabled, state.entityType, state.date]);
+
+ const handleDatePickerChange = (date: moment.MomentInput) => {
+ setState(prevState => ({
+ ...prevState,
+ date: moment(date).unix()
+ }));
+ };
+
+ const handleMenuChange: MenuProps["onClick"] = (e) => {
+ if (CONSTANTS.ENTITY_TYPES.includes(e.key as string)) {
+ minSize = Infinity;
+ maxSize = 0;
+ setState(prevState => ({
+ ...prevState,
+ entityType: e.key as string,
+ }));
+ }
+ };
+
+ const handleCalendarChange: MenuProps["onClick"] = (e) => {
+ if (CONSTANTS.TIME_PERIODS.includes(e.key as string)) {
+ setState(prevState => ({
+ ...prevState,
+ date: e.key
+ }));
+ }
+ };
+
+ const { date, entityType, heatmapResponse } = state;
+ const { inputPath, helpMessage, isInputPathValid } = inputPathState;
+
+ const menuCalendar = (
+ <Menu
+ defaultSelectedKeys={[date as string]}
+ onClick={handleCalendarChange}
+ selectable={true}>
+ <Menu.Item key={CONSTANTS.TIME_PERIODS[0]}>
+ 24 Hour
+ </Menu.Item>
+ <Menu.Item key={CONSTANTS.TIME_PERIODS[1]}>
+ 7 Days
+ </Menu.Item>
+ <Menu.Item key={CONSTANTS.TIME_PERIODS[2]}>
+ 90 Days
+ </Menu.Item>
+ <Menu.SubMenu title='Custom Select Last 90 Days'>
+ <Menu.Item key='heatmapDatePicker'>
+ <DatePicker
+ format="YYYY-MM-DD"
+ onChange={handleDatePickerChange}
+ onClick={(e) => { e.stopPropagation() }}
+ disabledDate={isDateDisabled} />
+ </Menu.Item>
+ </Menu.SubMenu>
+ </Menu>
+ );
+
+ const entityTypeMenu = (
+ <Menu
+ defaultSelectedKeys={[entityType]}
+ onClick={handleMenuChange}
+ selectable={true}>
+ <Menu.Item key={CONSTANTS.ENTITY_TYPES[2]}>
+ Volume
+ </Menu.Item>
+ <Menu.Item key={CONSTANTS.ENTITY_TYPES[1]}>
+ Bucket
+ </Menu.Item>
+ <Menu.Item key={CONSTANTS.ENTITY_TYPES[0]}>
+ Key
+ </Menu.Item>
+ </Menu>
+ );
+
+ function getErrorContent() {
+ if (!isHeatmapEnabled) {
+ return <Result
+ status='error'
+ title='Heatmap Not Available'
+ subTitle='Please ensure Heatmap is enabled in the configs and you have
sufficient permissions' />
+ }
+
+ if (treeEndpointFailed) {
+ return <Result
+ status='error'
+ title='Failed to fetch Heatmap'
+ subTitle='Check for any failed requests for more information' />
+ }
+ }
+
+ return (
+ <>
+ <div className='page-header-v2'>
+ Heatmap
+ </div>
+ <div className='data-container'>
+ {
+ (!isHeatmapEnabled || treeEndpointFailed)
+ ? getErrorContent()
+ : <div className='content-div'>
+ <div className='heatmap-header-section'>
+ <div className='heatmap-filter-section'>
+ <div className='path-input-container'>
+ <h4 style={{ paddingTop: '2%' }}>Path</h4>
+ <Form.Item className='path-input-element'
validateStatus={isInputPathValid} help={helpMessage}>
+ <Input.Search
+ allowClear
+ placeholder={CONSTANTS.ROOT_PATH}
+ name="inputPath"
+ value={inputPath}
+ onChange={handleChange}
+ onSearch={handleSubmit} />
+ </Form.Item>
+ </div>
+ <div className='entity-dropdown-button'>
+ <Dropdown
+ overlay={entityTypeMenu}
+ placement='bottomCenter'>
+ <Button>Entity Type: {entityType}<DownOutlined
/></Button>
+ </Dropdown>
+ </div>
+ <div className='date-dropdown-button'>
+ <Dropdown
+ overlay={menuCalendar}
+ placement='bottomLeft'>
+ <Button>Last {(date as number) > 100 ? new
Date((date as number) * 1000).toLocaleString() : date}<DownOutlined /></Button>
+ </Dropdown>
+ </div>
+ </div>
+ <div className='heatmap-legend-container'>
+ <div className='heatmap-legend-item'>
+ <div style={{ width: "13px", height: "13px",
backgroundColor: `${CONSTANTS.colourScheme["amberAlert"][0]}`, marginRight:
"5px" }}> </div>
+ <span>Less Accessed</span>
+ </div>
+ <div className='heatmap-legend-item'>
+ <div style={{ width: "13px", height: "13px",
backgroundColor: `${CONSTANTS.colourScheme["amberAlert"][8]}`, marginRight:
"5px" }}> </div>
+ <span>Moderate Accessed</span>
+ </div>
+ <div className='heatmap-legend-item'>
+ <div style={{ width: "13px", height: "13px",
backgroundColor: `${CONSTANTS.colourScheme["amberAlert"][20]}`, marginRight:
"5px" }}> </div>
+ <span>Most Accessed</span>
+ </div>
+ </div>
+ </div>
+ {isLoading
+ ? <Spin size='large' />
+ : (Object.keys(heatmapResponse).length > 0 &&
(heatmapResponse.label !== null || heatmapResponse.path !== null))
+ ? <div id="heatmap-plot-container">
+ <HeatmapPlot
+ data={heatmapResponse}
+ onClick={updateHeatmapParent}
+ colorScheme={CONSTANTS.colourScheme['amberAlert']}
+ entityType={entityType} />
+ </div>
+ : <Result
+ status='warning'
+ title='No Data available' />
+ }
+ </div>
+ }
+ </div>
+ </>
+ );
+}
+
+export default Heatmap;
\ No newline at end of file
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/routes-v2.tsx
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/routes-v2.tsx
index c3ff1b97e3..fb2dc0b9c4 100644
---
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/routes-v2.tsx
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/routes-v2.tsx
@@ -17,9 +17,6 @@
*/
import { lazy } from 'react';
-import { Heatmap } from '@/views/heatMap/heatmap';
-import NotFound from '@/v2/pages/notFound/notFound';
-
const Overview = lazy(() => import('@/v2/pages/overview/overview'));
const Volumes = lazy(() => import('@/v2/pages/volumes/volumes'))
const Buckets = lazy(() => import('@/v2/pages/buckets/buckets'));
@@ -29,6 +26,7 @@ const DiskUsage = lazy(() =>
import('@/v2/pages/diskUsage/diskUsage'));
const Containers = lazy(() => import('@/v2/pages/containers/containers'));
const Insights = lazy(() => import('@/v2/pages/insights/insights'));
const OMDBInsights = lazy(() => import('@/v2/pages/insights/omInsights'));
+const Heatmap = lazy(() => import('@/v2/pages/heatmap/heatmap'));
export const routesV2 = [
@@ -68,7 +66,6 @@ export const routesV2 = [
path: '/Om',
component: OMDBInsights
},
- // TODO: Replace with V2 heatmap once rea
{
path: '/Heatmap',
component: Heatmap
diff --git
a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/types/heatmap.types.ts
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/types/heatmap.types.ts
new file mode 100644
index 0000000000..a76db22a6f
--- /dev/null
+++
b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/types/heatmap.types.ts
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+export type InputPathValidTypes = 'error' | 'success' | 'warning' |
'validating' | undefined;
+
+export type HeatmapChild = {
+ label: string;
+ size: number;
+ accessCount: number;
+ color: number;
+}
+
+export type InputPathState = {
+ inputPath: string;
+ isInputPathValid: InputPathValidTypes;
+ helpMessage: string;
+}
+
+export type HeatmapResponse = {
+ label: string;
+ path: string;
+ maxAccessCount: number;
+ minAccessCount: number;
+ size: number;
+ children: HeatmapChild[];
+}
+
+export type HeatmapState = {
+ heatmapResponse: HeatmapResponse;
+ entityType: string;
+ date: string | number;
+}
+
+export interface IResponseError extends Error {
+ status?: number;
+}
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]