Skip to content

Commit

Permalink
[ui-metrics] Converted the Metrics page within Admin Server in ReactJ…
Browse files Browse the repository at this point in the history
…S (#3707)

* [ui-metrics] Converted the Metrics page within Admin Server in ReactJS

* Fixing issues for removing the errors

* Fixing the linting issues

* metricsNew

* Fixing lints in MetricsTable

* Fixing the tests-unnecessary changes

* Fixing the unit test, missing return type errors, any return type error

* Not hardcoding the metrics keys and generating dynamically

* WIP

* Updated the test for the review

* WIP

* fix unit test

Change-Id: I641dfe1152e184e8501dc718e49f4e038bbe69c4

* Fixed unit test for select with proper linting

* Fixing the style issues

* replace click with mouseDown for clicking the dropDown

* Filtering the metrics and removing python.gc, multiprocessing, auth starting metrics

* Fixing the linting issues

* Removing matchMedia as it is not required anymore

* Remove the unneccessary comments

* Proper heading to h4 tag

* remove the snake_case and add i18n

* Fixing the linting issues

* Fixing based on Bjorn's review comments

* Updating based on Tabriz's PR review comments

* Fixing linting issues

* Changing the metrics only to the required fields in metricsTable

* Fixing style issues

* Removing the shadow within the input filter

* Filtering done inside useEffect hook only and removed from elsewhere after saving state

* Revertung box shadow changes

* [ui-metrics] Converted the Metrics page within Admin Server in ReactJS

* Fixing issues for removing the errors

* Fixing the linting issues

* metricsNew

* Fixing lints in MetricsTable

* Fixing the tests-unnecessary changes

* Fixing the unit test, missing return type errors, any return type error

* Not hardcoding the metrics keys and generating dynamically

* WIP

* Updated the test for the review

* WIP

* fix unit test

Change-Id: I641dfe1152e184e8501dc718e49f4e038bbe69c4

* Fixed unit test for select with proper linting

* Fixing the style issues

* replace click with mouseDown for clicking the dropDown

* Filtering the metrics and removing python.gc, multiprocessing, auth starting metrics

* Fixing the linting issues

* Removing matchMedia as it is not required anymore

* Remove the unneccessary comments

* Proper heading to h4 tag

* remove the snake_case and add i18n

* Fixing the linting issues

* Fixing based on Bjorn's review comments

* Updating based on Tabriz's PR review comments

* Fixing linting issues

* Changing the metrics only to the required fields in metricsTable

* Fixing style issues

* Removing the shadow within the input filter

* Filtering done inside useEffect hook only and removed from elsewhere after saving state

* Revertung box shadow changes

* grey line removal WIP

* Box shadow final removal

* fixing indentation

* Removing unneccesary comments

---------

Co-authored-by: tabraiz <[email protected]>
  • Loading branch information
ananya-agarwal and tabraiz authored Jun 22, 2024
1 parent e118249 commit bdeccbd
Show file tree
Hide file tree
Showing 6 changed files with 434 additions and 70 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Licensed to Cloudera, Inc. under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. Cloudera, Inc. 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 '../../components/styles/variables';

.metrics-component {
background-color: $fluidx-gray-100;
padding: 0 24px 24px 24px;

.metrics-heading {
color: $fluidx-gray-700;
padding-left: $font-size-lg;
font-size: $fluidx-heading-h4-size;
line-height: $fluidx-heading-h4-line-height;
font-weight: $fluidx-heading-h4-weight;
}

.metrics-filter {
margin: $font-size-sm;
width: 30%;

input {
box-shadow: none;
-webkit-box-shadow: none;
}
}

.metrics-table th {
width: 30%;
}

.metrics-select {
border: 1px solid $fluidx-gray-600;
border-radius: $border-radius-base;
background-color: $fluidx-white;
min-width: 200px;
height: 32px;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Licensed to Cloudera, Inc. under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. Cloudera, Inc. 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 { render, waitFor, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import MetricsComponent from './MetricsComponent';

// Mock the API call to return sample metrics data
jest.mock('api/utils', () => ({
get: jest.fn(() =>
Promise.resolve({
metric: {
'queries.number': { value: 10 },
'requests.active': { count: 5 },
'requests.exceptions': { count: 2 },
'requests.response-time': {
'15m_rate': 20,
'1m_rate': 15,
'5m_rate': 18,
'75_percentile': 50,
'95_percentile': 60,
'999_percentile': 70,
'99_percentile': 55,
avg: 25,
count: 100,
max: 30,
mean_rate: 22,
min: 20,
std_dev: 5,
sum: 2500
},
'threads.daemon': { value: 3 },
'threads.total': { value: 6 },
users: { value: 50 },
'users.active': { value: 30 },
'users.active.total': { value: 40 }
}
})
)
}));

describe('MetricsComponent', () => {
// Test for filtering metrics based on input
test('Filtering metrics based on name column value', async () => {
render(<MetricsComponent />);

const filterInput = screen.getByPlaceholderText('Filter metrics...');
fireEvent.change(filterInput, { target: { value: 'value' } });

await waitFor(() => {
expect(screen.getByText('queries.number')).toBeInTheDocument();
expect(screen.getByText('threads.daemon')).toBeInTheDocument();
expect(screen.getByText('threads.total')).toBeInTheDocument();
expect(screen.getByText('users')).toBeInTheDocument();
expect(screen.getByText('users.active')).toBeInTheDocument();
expect(screen.getByText('users.active.total')).toBeInTheDocument();
expect(screen.queryByText('requests.active')).not.toBeInTheDocument();
expect(screen.queryByText('requests.exceptions')).toBeNull();
expect(screen.queryByText('requests.response-time')).toBeNull();
});
});

// Test for selecting a specific metric from the dropdown
test('selecting a specific metric from the dropdown filters the data using click events', async () => {
render(<MetricsComponent />);

await waitFor(() => screen.getByText('queries.number'));

const select = screen.getByTestId('metric-select').firstElementChild;
if (select) {
fireEvent.mouseDown(select);
}

const dropdown = document.querySelector('.ant-select');

const secondOption = dropdown?.querySelectorAll('.ant-select-item')[1];
if (secondOption) {
fireEvent.click(secondOption);
await waitFor(() => {
const headings = screen.queryAllByRole('heading', { level: 4 });
expect(headings).toHaveLength(1);
});
}
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Licensed to Cloudera, Inc. under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. Cloudera, Inc. 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, { useState, useEffect, useRef } from 'react';
import MetricsTable, { MetricsResponse } from './MetricsTable';
import { Spin, Input, Select, Alert } from 'antd';
import { get } from 'api/utils';
import { SearchOutlined } from '@ant-design/icons';
import './MetricsComponent.scss';

const { Option } = Select;

const MetricsComponent: React.FC = (): JSX.Element => {
const [metrics, setMetrics] = useState<MetricsResponse>();
const [filteredKeys, setFilteredKeys] = useState<string[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string>();
const [searchQuery, setSearchQuery] = useState<string>('');
const [selectedMetric, setSelectedMetric] = useState<string>('');
const [showAllTables, setShowAllTables] = useState(true);
const [filteredMetricsData, setFilteredMetricsData] = useState<MetricsData[]>([]);
const dropdownRef = useRef(null);

useEffect(() => {
const fetchData = async () => {
try {
const response = await get<MetricsResponse>('/desktop/metrics/', { format: 'json' });
setMetrics(response);
const keys = Object.keys(response.metric).filter(
key =>
!key.startsWith('auth') &&
!key.startsWith('multiprocessing') &&
!key.startsWith('python.gc')
);
setFilteredKeys(keys);
} catch (error) {
setError(error.message);
} finally {
setLoading(false);
}
};

fetchData();
}, []);

useEffect(() => {
if (!metrics) {
return;
}

const filteredData = parseMetricsData(metrics).filter(tableData =>
tableData.dataSource.some(data => data.name.toLowerCase().includes(searchQuery.toLowerCase()))
);

setFilteredMetricsData(filteredData);
}, [searchQuery, metrics]);

const parseMetricsData = (data: MetricsResponse) => {
return filteredKeys.map(key => ({
caption: key,
dataSource: Object.keys(data.metric[key]).map(subKey => ({
name: subKey,
value: data.metric[key][subKey]
}))
}));
};
const handleMetricChange = (value: string) => {
setSelectedMetric(value);
setShowAllTables(value === '');
};

const handleFilterInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchQuery(e.target.value);
};

return (
<div className="cuix antd metrics-component">
<Spin spinning={loading}>
{!error && (
<>
<Input
className="metrics-filter"
placeholder="Filter metrics..."
value={searchQuery}
onChange={handleFilterInputChange}
prefix={<SearchOutlined />}
/>

<Select
className="metrics-select"
//to make sure antd class gets applied
getPopupContainer={triggerNode => triggerNode.parentElement}
ref={dropdownRef}
value={selectedMetric}
onChange={handleMetricChange}
data-testid="metric-select"
>
<Option value="">All</Option>
{filteredKeys.map(key => (
<Option key={key} value={key}>
{key}
</Option>
))}
</Select>
</>
)}

{error && (
<Alert
message={`Error: ${error}`}
description="Error in displaying the Metrics!"
type="error"
/>
)}

{!error &&
filteredMetricsData.map((tableData, index) => (
<div key={index}>
{(showAllTables || selectedMetric === tableData.caption) && (
<MetricsTable caption={tableData.caption} dataSource={tableData.dataSource} />
)}
</div>
))}
</Spin>
</div>
);
};

export default MetricsComponent;
Loading

0 comments on commit bdeccbd

Please sign in to comment.