Skip to content

Commit 5e51066

Browse files
ui: Sum-by state optimization (#6102)
* SumBy state optimization to persist the default state and make use of it for quesries on load when available * [pre-commit.ci lite] apply automatic fixes * Linter fixes * [pre-commit.ci lite] apply automatic fixes * Test fixes * Initial loading fixed --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
1 parent 9c2af49 commit 5e51066

File tree

8 files changed

+190
-68
lines changed

8 files changed

+190
-68
lines changed

ui/packages/shared/profile/src/ProfileSelector/MetricsGraphSection.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ interface MetricsGraphSectionProps {
2929
querySelection: QuerySelection;
3030
profileSelection: ProfileSelection | null;
3131
comparing: boolean;
32-
sumBy: string[] | null;
32+
sumBy: string[] | undefined;
3333
defaultSumByLoading: boolean;
3434
queryClient: QueryServiceClient;
3535
queryExpressionString: string;
@@ -170,7 +170,7 @@ export function MetricsGraphSection({
170170
to={querySelection.to}
171171
profile={profileSelection}
172172
comparing={comparing}
173-
sumBy={querySelection.sumBy ?? sumBy ?? []}
173+
sumBy={sumBy ?? []}
174174
sumByLoading={defaultSumByLoading}
175175
setTimeRange={handleTimeRangeChange}
176176
addLabelMatcher={addLabelMatcher}

ui/packages/shared/profile/src/ProfileSelector/index.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -209,10 +209,6 @@ const ProfileSelector = ({
209209
const currentTo = timeRangeSelection.getToMs(true);
210210
const currentRangeKey = timeRangeSelection.getRangeKey();
211211
// Commit with refreshed time range
212-
console.log(
213-
'[draftExpression] setQueryExpression: committing with refreshed time range:',
214-
draftSelection.expression
215-
);
216212
commitDraft({
217213
from: currentFrom,
218214
to: currentTo,
@@ -332,7 +328,7 @@ const ProfileSelector = ({
332328
querySelection={querySelection}
333329
profileSelection={profileSelection}
334330
comparing={comparing}
335-
sumBy={querySelection.sumBy ?? []}
331+
sumBy={querySelection.sumBy}
336332
defaultSumByLoading={sumByLoading}
337333
queryClient={queryClient}
338334
queryExpressionString={queryExpressionString}

ui/packages/shared/profile/src/ProfileView/hooks/useResetStateOnProfileTypeChange.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import {useProfileFilters} from '../components/ProfileFilters/useProfileFilters'
1818
export const useResetStateOnProfileTypeChange = (): (() => void) => {
1919
const [groupBy, setGroupBy] = useURLState('group_by');
2020
const [curPath, setCurPath] = useURLState('cur_path');
21+
const [sumByA, setSumByA] = useURLState('sum_by_a');
22+
const [sumByB, setSumByB] = useURLState('sum_by_b');
2123
const {resetFilters} = useProfileFilters();
2224
const [sandwichFunctionName, setSandwichFunctionName] = useURLState('sandwich_function_name');
2325
const batchUpdates = useURLStateBatch();
@@ -34,6 +36,12 @@ export const useResetStateOnProfileTypeChange = (): (() => void) => {
3436
if (sandwichFunctionName !== undefined) {
3537
setSandwichFunctionName(undefined);
3638
}
39+
if (sumByA !== undefined) {
40+
setSumByA(undefined);
41+
}
42+
if (sumByB !== undefined) {
43+
setSumByB(undefined);
44+
}
3745

3846
resetFilters();
3947
});

ui/packages/shared/profile/src/SimpleMatchers/Select.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
// See the License for the specific language governing permissions and
1212
// limitations under the License.
1313

14-
import React, {useCallback, useEffect, useRef, useState} from 'react';
14+
import React, {Fragment, useCallback, useEffect, useRef, useState} from 'react';
1515

1616
import {Icon} from '@iconify/react';
1717
import cx from 'classnames';
@@ -350,7 +350,7 @@ const CustomSelect: React.FC<CustomSelectProps & Record<string, any>> = ({
350350
</div>
351351
) : (
352352
groupedFilteredItems.map(group => (
353-
<>
353+
<Fragment key={group.type}>
354354
{groupedFilteredItems.length > 1 &&
355355
groupedFilteredItems.every(g => g.type !== '') &&
356356
group.type !== '' ? (
@@ -369,7 +369,7 @@ const CustomSelect: React.FC<CustomSelectProps & Record<string, any>> = ({
369369
handleSelection={handleSelection}
370370
/>
371371
))}
372-
</>
372+
</Fragment>
373373
))
374374
)}
375375
</div>

ui/packages/shared/profile/src/hooks/useLabels.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
// See the License for the specific language governing permissions and
1212
// limitations under the License.
1313

14+
import {useEffect} from 'react';
15+
1416
import {LabelsRequest, LabelsResponse, QueryServiceClient, ValuesRequest} from '@parca/client';
1517
import {useGrpcMetadata} from '@parca/components';
1618
import {millisToProtoTimestamp, sanitizeLabelValue} from '@parca/utilities';
@@ -68,7 +70,9 @@ export const useLabelNames = (
6870
},
6971
});
7072

71-
console.log('Label names query result:', {data, error, isLoading});
73+
useEffect(() => {
74+
console.log('Label names query result:', {data, error, isLoading});
75+
}, [data, error, isLoading]);
7276

7377
return {
7478
result: {response: data, error: error as Error},

ui/packages/shared/profile/src/hooks/useQueryState.test.tsx

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -69,16 +69,33 @@ vi.mock('@parca/components/src/hooks/URLState/utils', async () => {
6969
};
7070
});
7171

72-
// Mock useSumBy to return the sumBy from URL params or undefined
72+
// Mock useSumBy with stateful behavior using React's useState
7373
vi.mock('../useSumBy', async () => {
7474
const actual = await vi.importActual('../useSumBy');
75+
const react = await import('react');
76+
7577
return {
7678
...actual,
77-
useSumBy: (_queryClient: any, _profileType: any, _timeRange: any, defaultValue: any) => ({
78-
sumBy: defaultValue,
79-
setSumBy: vi.fn(),
80-
isLoading: false,
81-
}),
79+
useSumBy: (
80+
_queryClient: any,
81+
_profileType: any,
82+
_timeRange: any,
83+
_draftProfileType: any,
84+
_draftTimeRange: any,
85+
defaultValue: any
86+
) => {
87+
const [draftSumBy, setDraftSumBy] = react.useState<string[] | undefined>(defaultValue);
88+
const [sumBy, setSumBy] = react.useState<string[] | undefined>(defaultValue);
89+
90+
return {
91+
sumBy,
92+
setSumBy,
93+
isLoading: false,
94+
draftSumBy,
95+
setDraftSumBy,
96+
isDraftSumByLoading: false,
97+
};
98+
},
8299
};
83100
});
84101

@@ -207,7 +224,9 @@ describe('useQueryState', () => {
207224
it('should update sumBy', async () => {
208225
const {result} = renderHook(() => useQueryState(), {wrapper: createWrapper()});
209226

227+
// sumBy only applies to delta profiles, so we need to set one first
210228
act(() => {
229+
result.current.setDraftExpression('process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{}');
211230
result.current.setDraftSumBy(['namespace', 'container']);
212231
});
213232

@@ -256,15 +275,15 @@ describe('useQueryState', () => {
256275
const {result} = renderHook(() => useQueryState(), {wrapper: createWrapper()});
257276

258277
act(() => {
259-
// Update multiple draft values
260-
result.current.setDraftExpression('memory:inuse_space:bytes:space:bytes{}');
278+
// Update multiple draft values (using delta profile since sumBy only applies to delta)
279+
result.current.setDraftExpression('memory:alloc_space:bytes:space:bytes:delta{}');
261280
result.current.setDraftTimeRange(7000, 8000, 'relative:minute|30');
262281
result.current.setDraftSumBy(['pod', 'node']);
263282
});
264283

265284
// All drafts should be updated
266285
expect(result.current.draftSelection.expression).toBe(
267-
'memory:inuse_space:bytes:space:bytes{}'
286+
'memory:alloc_space:bytes:space:bytes:delta{}'
268287
);
269288
expect(result.current.draftSelection.from).toBe(7000);
270289
expect(result.current.draftSelection.to).toBe(8000);
@@ -278,7 +297,7 @@ describe('useQueryState', () => {
278297
// Should only navigate once for all updates
279298
expect(mockNavigateTo).toHaveBeenCalledTimes(1);
280299
const [, params] = mockNavigateTo.mock.calls[0];
281-
expect(params.expression).toBe('memory:inuse_space:bytes:space:bytes{}');
300+
expect(params.expression).toBe('memory:alloc_space:bytes:space:bytes:delta{}');
282301
expect(params.from).toBe('7000');
283302
expect(params.to).toBe('8000');
284303
expect(params.time_selection).toBe('relative:minute|30');
@@ -430,9 +449,9 @@ describe('useQueryState', () => {
430449
it('should handle _b suffix correctly', async () => {
431450
const {result} = renderHook(() => useQueryState({suffix: '_b'}), {wrapper: createWrapper()});
432451

433-
// Update draft state
452+
// Update draft state (using delta profile since sumBy only applies to delta)
434453
act(() => {
435-
result.current.setDraftExpression('memory:inuse_space:bytes:space:bytes{}');
454+
result.current.setDraftExpression('memory:alloc_space:bytes:space:bytes:delta{}');
436455
result.current.setDraftTimeRange(3333, 4444, 'relative:hour|2');
437456
result.current.setDraftSumBy(['label_b']);
438457
});
@@ -445,7 +464,7 @@ describe('useQueryState', () => {
445464
await waitFor(() => {
446465
expect(mockNavigateTo).toHaveBeenCalled();
447466
const [, params] = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1];
448-
expect(params.expression_b).toBe('memory:inuse_space:bytes:space:bytes{}');
467+
expect(params.expression_b).toBe('memory:alloc_space:bytes:space:bytes:delta{}');
449468
expect(params.from_b).toBe('3333');
450469
expect(params.to_b).toBe('4444');
451470
expect(params.sum_by_b).toBe('label_b');
@@ -457,9 +476,9 @@ describe('useQueryState', () => {
457476
it('should not update URL until commit', async () => {
458477
const {result} = renderHook(() => useQueryState(), {wrapper: createWrapper()});
459478

460-
// Make multiple draft changes
479+
// Make multiple draft changes (using delta profile since sumBy only applies to delta)
461480
act(() => {
462-
result.current.setDraftExpression('memory:inuse_space:bytes:space:bytes{}');
481+
result.current.setDraftExpression('memory:alloc_space:bytes:space:bytes:delta{}');
463482
result.current.setDraftTimeRange(5000, 6000, 'relative:hour|3');
464483
result.current.setDraftSumBy(['namespace', 'pod']);
465484
});
@@ -476,7 +495,7 @@ describe('useQueryState', () => {
476495
await waitFor(() => {
477496
expect(mockNavigateTo).toHaveBeenCalledTimes(1);
478497
const [, params] = mockNavigateTo.mock.calls[0];
479-
expect(params.expression).toBe('memory:inuse_space:bytes:space:bytes{}');
498+
expect(params.expression).toBe('memory:alloc_space:bytes:space:bytes:delta{}');
480499
expect(params.from).toBe('5000');
481500
expect(params.to).toBe('6000');
482501
expect(params.sum_by).toBe('namespace,pod');
@@ -737,17 +756,17 @@ describe('useQueryState', () => {
737756

738757
describe('State persistence after page reload', () => {
739758
it('should retain committed values after page reload simulation', async () => {
740-
// Initial state
759+
// Initial state (using delta profile since sumBy only applies to delta)
741760
mockLocation.search =
742-
'?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds{}&from=1000&to=2000';
761+
'?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{}&from=1000&to=2000';
743762

744763
const {result: result1, unmount} = renderHook(() => useQueryState(), {
745764
wrapper: createWrapper(),
746765
});
747766

748-
// User makes changes to draft
767+
// User makes changes to draft (using delta profile since sumBy only applies to delta)
749768
act(() => {
750-
result1.current.setDraftExpression('memory:inuse_space:bytes:space:bytes{}');
769+
result1.current.setDraftExpression('memory:alloc_space:bytes:space:bytes:delta{}');
751770
result1.current.setDraftTimeRange(5000, 6000, 'relative:minute|15');
752771
result1.current.setDraftSumBy(['namespace', 'pod']);
753772
});
@@ -786,7 +805,7 @@ describe('useQueryState', () => {
786805

787806
// Verify state is loaded from URL after "reload"
788807
expect(result2.current.querySelection.expression).toBe(
789-
'memory:inuse_space:bytes:space:bytes{}'
808+
'memory:alloc_space:bytes:space:bytes:delta{}'
790809
);
791810
expect(result2.current.querySelection.from).toBe(5000);
792811
expect(result2.current.querySelection.to).toBe(6000);
@@ -795,7 +814,7 @@ describe('useQueryState', () => {
795814

796815
// Draft should be synced with URL state on page load
797816
expect(result2.current.draftSelection.expression).toBe(
798-
'memory:inuse_space:bytes:space:bytes{}'
817+
'memory:alloc_space:bytes:space:bytes:delta{}'
799818
);
800819
expect(result2.current.draftSelection.from).toBe(5000);
801820
expect(result2.current.draftSelection.to).toBe(6000);

0 commit comments

Comments
 (0)