Skip to content
Open
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
4 changes: 3 additions & 1 deletion ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"@typescript-eslint/eslint-plugin": "5.62.0",
"@typescript-eslint/parser": "5.62.0",
"arg": "5.0.2",
"babel-plugin-react-compiler": "1.0.0",
"chromatic": "11.29.0",
"css-loader": "6.11.0",
"eslint": "8.45.0",
Expand All @@ -78,7 +79,7 @@
"eslint-plugin-prettier": "5.5.5",
"eslint-plugin-promise": "6.6.0",
"eslint-plugin-react": "7.37.5",
"eslint-plugin-react-hooks": "4.6.2",
"eslint-plugin-react-hooks": "7.0.1",
"eslint-plugin-standard": "5.0.0",
"eslint-plugin-storybook": "0.12.0",
"eslint-plugin-typescript-enum": "2.1.0",
Expand All @@ -98,6 +99,7 @@
"postcss": "8.5.8",
"prettier": "3.6.2",
"prettier-plugin-tailwindcss": "^0.4.0",
"react-compiler-runtime": "1.0.0",
"react-is": "18.3.1",
"react-test-renderer": "18.3.1",
"replace-in-files": "3.0.0",
Expand Down
4 changes: 3 additions & 1 deletion ui/packages/app/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,14 @@
},
"devDependencies": {
"@types/lodash.throttle": "4.1.9",
"@vitejs/plugin-react-swc": "3.11.0",
"@vitejs/plugin-react": "4.7.0",
"babel-plugin-react-compiler": "1.0.0",
"css-loader": "6.11.0",
"eslint-config-prettier": "8.10.2",
"eslint-plugin-import": "2.32.0",
"jest": "29.7.0",
"jest-runtime": "29.7.0",
"react-compiler-runtime": "1.0.0",
"tslint": "6.1.3",
"tslint-config-prettier": "1.18.0",
"tslint-plugin-prettier": "2.3.0",
Expand Down
2 changes: 1 addition & 1 deletion ui/packages/app/web/src/components/ui/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ const Navbar = () => {
const compareA = queryParams.get('compare_a');
const compareB = queryParams.get('compare_b');

const queryParamsURL = parseParams(window.location.search);
const queryParamsURL = parseParams(location.search);

/* eslint-disable @typescript-eslint/naming-convention */
const {
Expand Down
7 changes: 6 additions & 1 deletion ui/packages/app/web/src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import {useCallback} from 'react';

import {GrpcWebFetchTransport} from '@protobuf-ts/grpcweb-transport';
import {useNavigate} from 'react-router-dom';
import {useLocation, useNavigate} from 'react-router-dom';

import {QueryServiceClient} from '@parca/client';
import {ParcaContextProvider, Spinner, URLStateProvider} from '@parca/components';
Expand All @@ -31,7 +31,12 @@ const queryClient = new QueryServiceClient(
);

const Profiles = () => {
'use no memo';
const navigate = useNavigate();
// useLocation() subscribes to react-router location changes so this component
// re-renders on navigate(). 'use no memo' ensures the re-render propagates to
// URLStateProvider, whose no-deps effect syncs state from window.location.search.
useLocation();
const isDarkMode = useAppSelector(selectDarkMode);

const navigateTo = useCallback(
Expand Down
21 changes: 18 additions & 3 deletions ui/packages/app/web/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,29 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import react from '@vitejs/plugin-react-swc';
import react from '@vitejs/plugin-react';
import {defineConfig} from 'vite';
import svgr from 'vite-plugin-svgr';

// https://vitejs.dev/config/
export default defineConfig({
// @ts-expect-error
plugins: [react(), svgr()],
// cast needed: dual @types/node versions create incompatible vite Plugin types
plugins: [
react({
babel: {
plugins: [
[
'babel-plugin-react-compiler',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if we should do this at the component packages level too? So that the benefits gets passed on to the downstream projects too.

Might have to change the lightweight tsc only build for those packages then.

{
target: '18',
compilationMode: 'infer',
},
],
],
},
}),
svgr(),
] as any,
base: './',
server: {
port: 3000,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright 2022 The Parca Authors
// 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.

/* eslint-disable jest-dom/prefer-to-have-value */

import {render} from '@testing-library/react';
import {describe, expect, it, vi} from 'vitest';

import {AbsoluteDate, DateTimeRange} from '../utils';
import AbsoluteDatePicker from './index';

describe('AbsoluteDatePicker', () => {
it('resyncs when an existing DateTimeRange instance is mutated', () => {
const range = new DateTimeRange(
new AbsoluteDate(new Date('2023-12-01T10:00:00Z')),
new AbsoluteDate(new Date('2023-12-01T15:30:00Z'))
);

const {rerender, getAllByRole} = render(
<AbsoluteDatePicker range={range} onChange={vi.fn()} />
);

const [startInput, endInput] = getAllByRole('textbox');
expect((startInput as HTMLInputElement).value).toBe('2023-12-01 10:00:00');
expect((endInput as HTMLInputElement).value).toBe('2023-12-01 15:30:00');

range.from = new AbsoluteDate(new Date('2023-12-02T08:15:00Z'));
range.to = new AbsoluteDate(new Date('2023-12-02T09:45:00Z'));

rerender(<AbsoluteDatePicker range={range} onChange={vi.fn()} />);

expect((startInput as HTMLInputElement).value).toBe('2023-12-02 08:15:00');
expect((endInput as HTMLInputElement).value).toBe('2023-12-02 09:45:00');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

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

import {DateTimePicker} from '../../DateTimePicker';
import {AbsoluteDate, DateTimeRange, RelativeDate, getHistoricalDate} from '../utils';
Expand All @@ -22,52 +22,24 @@ interface AbsoluteDatePickerProps {
}

const AbsoluteDatePicker = ({range, onChange}: AbsoluteDatePickerProps): JSX.Element => {
const dateFromInRelative = useMemo(() => range.from as RelativeDate, [range.from]);
const dateToInRelative = useMemo(() => range.to as RelativeDate, [range.to]);

const [from, setFrom] = useState<AbsoluteDate>(
range.from.isRelative()
? new AbsoluteDate(
getHistoricalDate({
unit: dateFromInRelative.unit,
value: dateFromInRelative.value,
})
)
: (range.from as AbsoluteDate)
);
const [to, setTo] = useState<AbsoluteDate>(
range.to.isRelative()
const toAbsolute = (d: RelativeDate | AbsoluteDate): AbsoluteDate =>
d.isRelative()
? new AbsoluteDate(
getHistoricalDate({
unit: dateToInRelative.unit,
value: dateToInRelative.value,
})
getHistoricalDate({unit: (d as RelativeDate).unit, value: (d as RelativeDate).value})
)
: (range.to as AbsoluteDate)
);
: (d as AbsoluteDate);

const [from, setFrom] = useState<AbsoluteDate>(() => toAbsolute(range.from));
const [to, setTo] = useState<AbsoluteDate>(() => toAbsolute(range.to));
const [prevRangeFrom, setPrevRangeFrom] = useState(range.from);
const [prevRangeTo, setPrevRangeTo] = useState(range.to);

useEffect(() => {
setFrom(
range.from.isRelative()
? new AbsoluteDate(
getHistoricalDate({
unit: dateFromInRelative.unit,
value: dateFromInRelative.value,
})
)
: (range.from as AbsoluteDate)
);
setTo(
range.to.isRelative()
? new AbsoluteDate(
getHistoricalDate({
unit: dateToInRelative.unit,
value: dateToInRelative.value,
})
)
: (range.to as AbsoluteDate)
);
}, [dateFromInRelative, dateToInRelative, range.from, range.to]);
if (prevRangeFrom !== range.from || prevRangeTo !== range.to) {
setPrevRangeFrom(range.from);
setPrevRangeTo(range.to);
setFrom(toAbsolute(range.from));
setTo(toAbsolute(range.to));
}

return (
<div className="flex flex-col w-[80%] mx-auto">
Expand Down
3 changes: 3 additions & 0 deletions ui/packages/shared/components/src/ResponsiveSvg/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.

/* eslint-disable react-hooks/set-state-in-effect */

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

import {useContainerDimensions} from '@parca/hooks';

interface Props {
children: JSX.Element;
[x: string]: any;

Check warning on line 22 in ui/packages/shared/components/src/ResponsiveSvg/index.tsx

View workflow job for this annotation

GitHub Actions / UI Test and Lint

Unexpected any. Specify a different type
}

const addPropsToChildren = (children: JSX.Element, props: {[x: string]: any}): JSX.Element[] => {

Check warning on line 25 in ui/packages/shared/components/src/ResponsiveSvg/index.tsx

View workflow job for this annotation

GitHub Actions / UI Test and Lint

Unexpected any. Specify a different type
const addProps = (child: JSX.Element): JSX.Element => ({
...child,
props: {
Expand All @@ -33,6 +35,7 @@
};

const ResponsiveSvg = (props: Props): JSX.Element => {
'use no memo';
const {children} = props;
const {ref, dimensions} = useContainerDimensions();
const {width} = dimensions ?? {width: 0};
Expand Down
1 change: 1 addition & 0 deletions ui/packages/shared/components/src/Table/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
enableHighlighting,
shouldHighlightRow,
rows,
}: RowRendererProps<any>): JSX.Element => {

Check warning on line 56 in ui/packages/shared/components/src/Table/index.tsx

View workflow job for this annotation

GitHub Actions / UI Test and Lint

Unexpected any. Specify a different type
return (
<tr
key={row.id}
Expand Down Expand Up @@ -139,6 +139,7 @@
scrollToIndex,
estimatedRowHeight = 26,
}: Props<T>): JSX.Element => {
'use no memo';
const [sorting, setSorting] = useState<SortingState>(initialSorting);
const tableContainerRef = useRef<HTMLDivElement>(null);
const scrollingRef = useRef<number>();
Expand Down
3 changes: 3 additions & 0 deletions ui/packages/shared/profile/src/GraphTooltipArrow/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

/* eslint-disable react-hooks/refs */

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

import {flip, offset, shift, useFloating, type VirtualElement} from '@floating-ui/react';
Expand Down Expand Up @@ -39,6 +41,7 @@ function createPositionedVirtualElement(contextElement: Element, x = 0, y = 0):
}

const GraphTooltip = ({children, contextElement}: GraphTooltipProps): React.JSX.Element => {
'use no memo';
const [isPositioned, setIsPositioned] = useState(false);

const {refs, floatingStyles, update} = useFloating({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright 2022 The Parca Authors
// 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 {useRef} from 'react';

import {act, fireEvent, render} from '@testing-library/react';
import {beforeAll, describe, expect, it, vi} from 'vitest';

import SuggestionsList, {Suggestion, Suggestions} from './SuggestionsList';

vi.mock('@parca/components', () => ({
RefreshButton: ({title}: {title: string}) => <button type="button">{title}</button>,
useParcaContext: () => ({
loader: <div>loading</div>,
}),
}));

beforeAll(() => {
Element.prototype.scrollIntoView = vi.fn();
});

const TestHarness = ({inputKey = 'initial'}: {inputKey?: string}): JSX.Element => {
const inputRef = useRef<HTMLTextAreaElement | null>(null);
const suggestions = new Suggestions();
suggestions.labelNames.push(new Suggestion('labelName', 'na', 'namespace'));

return (
<div>
<textarea key={inputKey} ref={inputRef} />
<SuggestionsList
suggestions={suggestions}
applySuggestion={vi.fn()}
inputRef={inputRef}
runQuery={vi.fn()}
focusedInput
isLabelNamesLoading={false}
isLabelValuesLoading={false}
shouldTrimPrefix={false}
refetchLabelValues={vi.fn(async () => {})}
refetchLabelNames={vi.fn(async () => {})}
/>
</div>
);
};

describe('SuggestionsList', () => {
it('rebinds keyboard listeners when the textarea ref points to a remounted node', () => {
const {rerender, getByRole, getByText} = render(<TestHarness inputKey="first" />);

rerender(<TestHarness inputKey="second" />);

const textarea = getByRole('textbox');
act(() => {
fireEvent.keyDown(textarea, {key: 'ArrowDown'});
});

// eslint-disable-next-line jest-dom/prefer-to-have-class
expect(getByText('namespace').className).toContain('bg-indigo-600');
});
});
Loading
Loading