Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions src/gprofiler/frontend/src/components/profiles/ProfilesViews.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/
}

import _ from 'lodash';

Check warning on line 19 in src/gprofiler/frontend/src/components/profiles/ProfilesViews.jsx

View workflow job for this annotation

GitHub Actions / build

Run autofix to sort these imports!
import { useCallback, useContext, useEffect, useMemo, useState } from 'react';

import useGetFgMetrics from '@/api/hooks/useGetFgMetrics';
Expand Down Expand Up @@ -170,12 +170,17 @@
}, [searchedData, framesSelected]);

useEffect(() => {
if (!_.isEmpty(flameGraphData[0]) && flameGraphData[0]?.children?.length === 0) {
setIsFGEmpty(true);
} else {
setIsFGEmpty(false);
// Only update isFGEmpty for views that depend on flamegraph data
// Only adhoc view doesn't depend on flamegraph data and manages its own empty state
const viewsDependingOnFgData = [PROFILES_VIEWS.flamegraph, PROFILES_VIEWS.table, PROFILES_VIEWS.service, PROFILES_VIEWS.html];
if (viewsDependingOnFgData.includes(viewMode)) {
if (!_.isEmpty(flameGraphData[0]) && flameGraphData[0]?.children?.length === 0) {
setIsFGEmpty(true);
} else {
setIsFGEmpty(false);
}
}
}, [flameGraphData, setIsFGEmpty]);
}, [flameGraphData, setIsFGEmpty, viewMode]);

useEffect(() => {
if (searchValue) {
Expand Down Expand Up @@ -244,14 +249,18 @@
[setHoverData, searchedData]
);

// Always render adhoc view when selected, regardless of isFGEmpty state
// since adhoc view has its own empty state handling
if (viewMode === PROFILES_VIEWS.adhoc) {
return <AdhocProfilingView />;
}

return viewMode === PROFILES_VIEWS.service ? (
<ServiceView />
) : viewMode === PROFILES_VIEWS.table ? (
<TableView timeSelection={timeSelection} rows={tableViewData} filteredData={filteredData} />
) : viewMode === PROFILES_VIEWS.html ? (
<HtmlView lastHtml={lastHtmlData} />
) : viewMode === PROFILES_VIEWS.adhoc ? (
<AdhocProfilingView />
) : !isFGEmpty ? (
<FlamegraphView
flameGraphData={flameGraphData}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,23 @@ const ProfilesViewsWrapper = () => {
const { viewMode } = useContext(SelectorsContext);
const isTableView = viewMode === PROFILES_VIEWS.table;
const isFlameGraphView = viewMode === PROFILES_VIEWS.flamegraph;
const isAdhocView = viewMode === PROFILES_VIEWS.adhoc;
// Adhoc view doesn't depend on flamegraph data
const shouldDisplayView = isFgDisplayed || isAdhocView;
return (
<FgHoverContextProvider>
<ErrorBoundary FallbackComponent={ErrorFallback}>
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%', alignItems: 'stretch' }}>
<Box sx={{ p: isTableView ? 0 : 4, height: '100%' }}>
<div
style={{
display: isFgDisplayed ? 'inherit' : 'none',
display: shouldDisplayView ? 'inherit' : 'none',
overflow: 'hidden',
width: '100%',
height: isFullScreen ? '90vh' : isFlameGraphView ? 'calc(100vh - 200px)' : 'auto',
maxHeight: isFullScreen ? '90vh' : isFlameGraphView ? 'calc(100vh - 200px)' : 'auto',
}}>
{isFgDisplayed && <ProfilesViews />}
{shouldDisplayView && <ProfilesViews />}
</div>
{isFlameGraphView && (
<Box
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import { useContext, useEffect } from 'react';
import useGetFgData from '../../api/hooks/useGetFgData';
import useGetFgMetrics from '../../api/hooks/useGetFgMetrics';
import UsePageTitle from '../../hooks/usePageTitle';
import { FgContext } from '../../states';
import { FgContext, SelectorsContext } from '../../states';
import { PROFILES_VIEWS } from '../../utils/consts';
import { getUpdatedFgData } from '../../utils/fgUtils';
import FGLoader from './FGLoader';
import ProfilesEmptyState from './ProfilesEmptyState';
Expand All @@ -43,6 +44,8 @@ const ProfilesWrapper = () => {
setInstanceType,
} = useContext(FgContext);

const { viewMode } = useContext(SelectorsContext);

const { data, error, lastWeekDataError, lastWeekData, isLastWeekDataLoading, loading } = useGetFgData({});
const {
metricsData,
Expand Down Expand Up @@ -108,12 +111,15 @@ const ProfilesWrapper = () => {
}
}, [instanceTypeData, setInstanceTypeLoading, setInstanceType, instanceTypeDataLoading]);

// Adhoc view doesn't depend on flamegraph data, so always show it when selected
const shouldShowView = isFgDisplayed || viewMode === PROFILES_VIEWS.adhoc;

return (
<>
{loading || isLastWeekDataLoading ? (
<FGLoader />
) : (
<>{!isFgDisplayed ? <ProfilesEmptyState errorMessage={error} /> : <ProfilesViewsWrapper />}</>
<>{!shouldShowView ? <ProfilesEmptyState errorMessage={error} /> : <ProfilesViewsWrapper />}</>
)}
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
* limitations under the License.
*/

import { useContext, useEffect, useState } from 'react';

Check warning on line 17 in src/gprofiler/frontend/src/components/profiles/views/adhoc/AdhocProfilingView.jsx

View workflow job for this annotation

GitHub Actions / build

Run autofix to sort these imports!
import { Box, Typography, Select, MenuItem, FormControl, InputLabel, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Button } from '@mui/material';
import { Box, Typography, Select, MenuItem, FormControl, InputLabel, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Button, IconButton, Collapse } from '@mui/material';
import { SelectorsContext } from '@/states';
import { FilterTagsContext } from '@/states/filters/FiltersTagsContext';
import useFetchWithRequest from '@/api/useFetchWithRequest';
Expand All @@ -24,12 +24,15 @@
import { getStartEndDateTimeFromSelection } from '@/api/utils';
import { format } from 'date-fns';
import Flexbox from '@/components/common/layout/Flexbox';
import Icon from '@/components/common/icon/Icon';
import { ICONS_NAMES } from '@/components/common/icon/iconsData';

const AdhocProfilingView = () => {
const { selectedService, timeSelection, selectedHost } = useContext(SelectorsContext);
const { activeFilterTag } = useContext(FilterTagsContext);
const [selectedFile, setSelectedFile] = useState(null);
const [selectedFileContent, setSelectedFileContent] = useState(null);
const [isTableExpanded, setIsTableExpanded] = useState(true);

const timeParams = getStartEndDateTimeFromSelection(timeSelection);

Expand Down Expand Up @@ -99,65 +102,88 @@

return (
<Flexbox column sx={{ height: '100%', width: '100%', p: 2 }}>
<Box sx={{ mb: 2, display: 'flex', alignItems: 'center', gap: 2 }}>
<Box
sx={{
mb: 2,
display: 'flex',
alignItems: 'center',
gap: 2,
backgroundColor: filesData && filesData.length > 0 ? 'grey.100' : 'transparent',
p: filesData && filesData.length > 0 ? 2 : 0,
borderRadius: 1,
cursor: filesData && filesData.length > 0 ? 'pointer' : 'default',
'&:hover': filesData && filesData.length > 0 ? {
backgroundColor: 'grey.200',
} : {}
}}
onClick={() => filesData && filesData.length > 0 && setIsTableExpanded(!isTableExpanded)}
>
<Typography variant="h6">Adhoc Profiling</Typography>
{filesData && filesData.length > 0 && (
<FormControl sx={{ minWidth: 200 }}>
<InputLabel id="select-file-label">Select Flamegraph File</InputLabel>
<Select
labelId="select-file-label"
value={selectedFile?.filename || ''}
label="Select Flamegraph File"
onChange={handleFileSelect}
>
{filesData.map((file) => (
<MenuItem key={file.filename} value={file.filename}>
{format(new Date(file.timestamp), 'yyyy-MM-dd HH:mm:ss')} - {file.hostname || 'N/A'}
</MenuItem>
))}
</Select>
</FormControl>
<>
<FormControl sx={{ minWidth: 200 }}>
<InputLabel id="select-file-label">Select Flamegraph File</InputLabel>
<Select
labelId="select-file-label"
value={selectedFile?.filename || ''}
label="Select Flamegraph File"
onChange={handleFileSelect}
onClick={(e) => e.stopPropagation()}
>
{filesData.map((file) => (
<MenuItem key={file.filename} value={file.filename}>
{format(new Date(file.timestamp), 'yyyy-MM-dd HH:mm:ss')} - {file.hostname || 'N/A'}
</MenuItem>
))}
</Select>
</FormControl>
<Box sx={{ ml: 'auto', display: 'flex', alignItems: 'center' }}>
<Icon name={isTableExpanded ? ICONS_NAMES.ChevronDown : ICONS_NAMES.ChevronRight} />
</Box>
</>
)}
</Box>

{filesData && filesData.length > 0 ? (
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', gap: 2, height: 'calc(100vh - 300px)' }}>
<TableContainer component={Paper} sx={{ maxHeight: 300, overflowY: 'auto', flexShrink: 0 }}>
<Table stickyHeader size="small">
<TableHead>
<TableRow>
<TableCell>Timestamp</TableCell>
<TableCell>Hostname</TableCell>
<TableCell>Size</TableCell>
<TableCell>Action</TableCell>
</TableRow>
</TableHead>
<TableBody>
{filesData.map((file) => (
<TableRow
key={file.filename}
onClick={() => handleRowClick(file)}
selected={selectedFile?.filename === file.filename}
sx={{ cursor: 'pointer', '&:hover': { backgroundColor: 'action.hover' } }}
>
<TableCell>{format(new Date(file.timestamp), 'yyyy-MM-dd HH:mm:ss')}</TableCell>
<TableCell>{file.hostname || 'N/A'}</TableCell>
<TableCell>{(file.size / 1024).toFixed(2)} KB</TableCell>
<TableCell>
<Button size="small" onClick={() => handleRowClick(file)}>View</Button>
</TableCell>
<Collapse in={isTableExpanded}>
<TableContainer component={Paper} sx={{ maxHeight: 300, overflowY: 'auto', flexShrink: 0 }}>
<Table stickyHeader size="small">
<TableHead>
<TableRow>
<TableCell>Timestamp</TableCell>
<TableCell>Hostname</TableCell>
<TableCell>Size</TableCell>
<TableCell>Action</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</TableHead>
<TableBody>
{filesData.map((file) => (
<TableRow
key={file.filename}
onClick={() => handleRowClick(file)}
selected={selectedFile?.filename === file.filename}
sx={{ cursor: 'pointer', '&:hover': { backgroundColor: 'action.hover' } }}
>
<TableCell>{format(new Date(file.timestamp), 'yyyy-MM-dd HH:mm:ss')}</TableCell>
<TableCell>{file.hostname || 'N/A'}</TableCell>
<TableCell>{(file.size / 1024).toFixed(2)} KB</TableCell>
<TableCell>
<Button size="small" onClick={() => handleRowClick(file)}>View</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Collapse>

{contentLoading && <Typography>Loading flamegraph content...</Typography>}
{contentError && <Typography color="error">Error loading flamegraph content: {contentError.message}</Typography>}
{selectedFileContent ? (
<Box
sx={{
flexGrow: 1,
<Box
sx={{
flexGrow: 1,
width: '100%',
minHeight: 0,
display: 'flex',
Expand Down
Loading