diff --git a/proto/buf.lock b/proto/buf.lock index 2282a530c44..6bd8f15b207 100644 --- a/proto/buf.lock +++ b/proto/buf.lock @@ -4,8 +4,8 @@ deps: - remote: buf.build owner: googleapis repository: googleapis - commit: 8bc2c51e08c447cd8886cdea48a73e14 - digest: shake256:a969155953a5cedc5b2df5b42c368f2bc66ff8ce1804bc96e0f14ff2ee8a893687963058909df844d1643cdbc98ff099d2daa6bc9f9f5b8886c49afdc60e19af + commit: e7f8d366f5264595bcc4cd4139af9973 + digest: shake256:e5e5f1c12f82e028ea696faa43b4f9dc6258a6d1226282962a8c8b282e10946281d815884f574bd279ebd9cd7588629beb3db17b892af6c33b56f92f8f67f509 - remote: buf.build owner: grpc-ecosystem repository: grpc-gateway diff --git a/ui/packages/app/web/src/App.tsx b/ui/packages/app/web/src/App.tsx index a594c8b4c56..f9482fa2ad6 100644 --- a/ui/packages/app/web/src/App.tsx +++ b/ui/packages/app/web/src/App.tsx @@ -71,15 +71,13 @@ const App = () => {
-
- - } /> - } /> - } /> - } /> - } /> - -
+ + } /> + } /> + } /> + } /> + } /> + diff --git a/ui/packages/app/web/src/components/ui/Navbar.tsx b/ui/packages/app/web/src/components/ui/Navbar.tsx index 5c5f5b188bc..68061613d2c 100644 --- a/ui/packages/app/web/src/components/ui/Navbar.tsx +++ b/ui/packages/app/web/src/components/ui/Navbar.tsx @@ -125,7 +125,10 @@ const Navbar = () => { }; return ( - + {({open}) => ( <>
@@ -159,92 +162,90 @@ const Navbar = () => {
-
- -
+
+ )} +
+ ) : item.external === true ? ( + + {item.label} + + ) : ( + + {item.label} + + ); + })} + @@ -255,11 +256,7 @@ const Navbar = () => {
- +
diff --git a/ui/packages/app/web/src/pages/index.tsx b/ui/packages/app/web/src/pages/index.tsx index 18a811a38df..273e0bbc8fe 100644 --- a/ui/packages/app/web/src/pages/index.tsx +++ b/ui/packages/app/web/src/pages/index.tsx @@ -58,11 +58,13 @@ const Profiles = () => { isDarkMode, }} > - +
+ +
); diff --git a/ui/packages/app/web/src/pages/layouts/ThemeProvider.tsx b/ui/packages/app/web/src/pages/layouts/ThemeProvider.tsx index 69dc60ed29c..bc3aee8734c 100644 --- a/ui/packages/app/web/src/pages/layouts/ThemeProvider.tsx +++ b/ui/packages/app/web/src/pages/layouts/ThemeProvider.tsx @@ -69,7 +69,16 @@ const ThemeProvider = ({children}: {children: React.ReactNode}) => { } }, [darkMode, isSystemSettingsTheme]); - return
{children}
; + return ( +
+ {children} +
+ ); }; export default ThemeProvider; diff --git a/ui/packages/app/web/src/pages/settings.tsx b/ui/packages/app/web/src/pages/settings.tsx index bbeb8388678..e3ed9191f28 100644 --- a/ui/packages/app/web/src/pages/settings.tsx +++ b/ui/packages/app/web/src/pages/settings.tsx @@ -15,8 +15,8 @@ import {UserPreferences} from '@parca/components'; const SettingsPage = () => { return ( -
-
+
+

Visualisation Settings

diff --git a/ui/packages/shared/components/src/DateTimeRangePicker/index.tsx b/ui/packages/shared/components/src/DateTimeRangePicker/index.tsx index ba2dd4f9b51..f28950f4da8 100644 --- a/ui/packages/shared/components/src/DateTimeRangePicker/index.tsx +++ b/ui/packages/shared/components/src/DateTimeRangePicker/index.tsx @@ -39,7 +39,7 @@ const DateTimeRangePicker = ({onRangeSelection, range}: DateTimeRangePickerProps return ( -
+
- +
{element === undefined ? ( diff --git a/ui/packages/shared/components/src/ParcaContext/index.tsx b/ui/packages/shared/components/src/ParcaContext/index.tsx index e434aa903f9..bf36b08be0a 100644 --- a/ui/packages/shared/components/src/ParcaContext/index.tsx +++ b/ui/packages/shared/components/src/ParcaContext/index.tsx @@ -75,7 +75,7 @@ export const defaultValue: Props = { loader: , noDataPrompt: , profileExplorer: { - PaddingX: 58, + PaddingX: 32, metricsGraph: { maxHeightStyle: { default: 'calc(47vw - 24px)', diff --git a/ui/packages/shared/components/src/Skeletons/IcicleGraphSkeleton.tsx b/ui/packages/shared/components/src/Skeletons/IcicleGraphSkeleton.tsx index 6d030eaf33f..41f40a8bdd8 100644 --- a/ui/packages/shared/components/src/Skeletons/IcicleGraphSkeleton.tsx +++ b/ui/packages/shared/components/src/Skeletons/IcicleGraphSkeleton.tsx @@ -59,7 +59,7 @@ const IcicleGraphSkeleton = ({isHalfScreen, isDarkMode}: Props): JSX.Element => repeatCount="indefinite" > - + repeatCount="indefinite" > - + (function (agg: Series[], s: MetricsSeriesPb) { if (s.labelset !== undefined) { const metric = s.labelset.labels.sort((a, b) => a.name.localeCompare(b.name)); @@ -184,10 +186,7 @@ export const RawMetricsGraph = ({ }); /* Scale */ - const xScale = d3 - .scaleUtc() - .domain([from, to]) - .range([0, width - 2.5 * margin]); + const xScale = d3.scaleUtc().domain([from, to]).range([0, graphWidth]); const yScale = d3 .scaleLinear() @@ -555,8 +554,8 @@ export const RawMetricsGraph = ({ ))} diff --git a/ui/packages/shared/profile/src/ProfileExplorer/ProfileExplorerCompare.tsx b/ui/packages/shared/profile/src/ProfileExplorer/ProfileExplorerCompare.tsx index dc838be9f03..5ed09e5edc5 100644 --- a/ui/packages/shared/profile/src/ProfileExplorer/ProfileExplorerCompare.tsx +++ b/ui/packages/shared/profile/src/ProfileExplorer/ProfileExplorerCompare.tsx @@ -11,8 +11,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +import {useState} from 'react'; + import {QueryServiceClient} from '@parca/client'; -import {Card, useURLState} from '@parca/components'; +import {useURLState} from '@parca/components'; import {Query} from '@parca/parser'; import type {NavigateFunction} from '@parca/utilities'; @@ -48,6 +50,9 @@ const ProfileExplorerCompare = ({ closeProfile, navigateTo, }: ProfileExplorerCompareProps): JSX.Element => { + const [showMetricsGraph, setShowMetricsGraph] = useState(true); + const [showButton, setShowButton] = useState(false); + const closeProfileA = (): void => { closeProfile('A'); }; @@ -61,8 +66,18 @@ const ProfileExplorerCompare = ({ return ( <> -
- +
{ + if (!showMetricsGraph) return; + setShowButton(true); + }} + onMouseLeave={() => { + if (!showMetricsGraph) return; + setShowButton(false); + }} + > +
- - +
+
- +
{profileA != null && profileB != null ? (
- - - +
) : (
diff --git a/ui/packages/shared/profile/src/ProfileExplorer/ProfileExplorerSingle.tsx b/ui/packages/shared/profile/src/ProfileExplorer/ProfileExplorerSingle.tsx index 83c7db6a517..0b212aaab71 100644 --- a/ui/packages/shared/profile/src/ProfileExplorer/ProfileExplorerSingle.tsx +++ b/ui/packages/shared/profile/src/ProfileExplorer/ProfileExplorerSingle.tsx @@ -11,8 +11,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +import {useState} from 'react'; + import {QueryServiceClient} from '@parca/client'; -import {Card} from '@parca/components'; import type {NavigateFunction} from '@parca/utilities'; import {ProfileSelection, ProfileViewWithData} from '..'; @@ -35,9 +36,22 @@ const ProfileExplorerSingle = ({ profile, navigateTo, }: ProfileExplorerSingleProps): JSX.Element => { + const [showMetricsGraph, setShowMetricsGraph] = useState(true); + const [showButton, setShowButton] = useState(false); + return ( <> - +
{ + if (!showMetricsGraph) return; + setShowButton(true); + }} + onMouseLeave={() => { + if (!showMetricsGraph) return; + setShowButton(false); + }} + > - +
+ {profile != null ? ( - - - + ) : ( <> )} diff --git a/ui/packages/shared/profile/src/ProfileSelector/index.tsx b/ui/packages/shared/profile/src/ProfileSelector/index.tsx index ce9c419541d..dd7f6a4e60d 100644 --- a/ui/packages/shared/profile/src/ProfileSelector/index.tsx +++ b/ui/packages/shared/profile/src/ProfileSelector/index.tsx @@ -11,10 +11,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React, {useEffect, useMemo, useRef, useState} from 'react'; +import React, {Dispatch, SetStateAction, useEffect, useMemo, useRef, useState} from 'react'; import {Switch} from '@headlessui/react'; import {RpcError} from '@protobuf-ts/runtime-rpc'; +import cx from 'classnames'; import Select, {type SelectInstance} from 'react-select'; import {Label, ProfileTypesResponse, QueryServiceClient} from '@parca/client'; @@ -63,6 +64,9 @@ interface ProfileSelectorProps { comparing: boolean; navigateTo: NavigateFunction; suffix?: string; + showMetricsGraph: boolean; + displayHideMetricsGraphButton: boolean; + setDisplayHideMetricsGraphButton: Dispatch>; } export interface IProfileTypesResult { @@ -101,6 +105,9 @@ const ProfileSelector = ({ profileSelection, comparing, navigateTo, + showMetricsGraph, + displayHideMetricsGraphButton, + setDisplayHideMetricsGraphButton, }: ProfileSelectorProps): JSX.Element => { const { loading: profileTypesLoading, @@ -281,9 +288,9 @@ const ProfileSelector = ({ return ( <> -
-
-
+
+
+
-
+
@@ -309,7 +316,7 @@ const ProfileSelector = ({ className={`${ advancedModeForQueryBrowser ? 'bg-indigo-600' - : 'bg-gray-400 dark:bg-gray-900' + : 'bg-gray-400 dark:bg-gray-800' } relative inline-flex h-[20px] w-[44px] shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-white/75`} > @@ -365,7 +372,7 @@ const ProfileSelector = ({ )}
-
+
@@ -383,6 +390,7 @@ const ProfileSelector = ({ placeholder="Labels..." styles={{ indicatorSeparator: () => ({display: 'none'}), + menu: provided => ({...provided, width: 'max-content'}), }} isDisabled={!profileType.delta} ref={sumByRef} @@ -426,75 +434,91 @@ const ProfileSelector = ({
{comparing && closeProfile()} icon={} />}
-
-
- {querySelection.expression !== undefined && - querySelection.expression.length > 0 && - querySelection.from !== undefined && - querySelection.to !== undefined ? ( -
- { - const from = range.getFromMs(); - const to = range.getToMs(); - let mergedProfileParams = {}; - if (query.profileType().delta) { - mergedProfileParams = {mergeFrom: from, mergeTo: to}; - } - setTimeRangeSelection(range); - selectQuery({ - expression: queryExpressionString, - from, - to, - timeSelection: range.getRangeKey(), - ...mergedProfileParams, - }); - }} - addLabelMatcher={addLabelMatcher} - onPointClick={( - timestamp: number, - labels: Label[], - queryExpression: string, - duration: number - ) => { - // TODO: Pass the query object via click rather than queryExpression - let query = Query.parse(queryExpression); - labels.forEach(l => { - const [newQuery, updated] = query.setMatcher(l.name, l.value); - if (updated) { - query = newQuery; - } - }); - - const durationInMilliseconds = duration / 1000000; // duration is in nanoseconds - const mergeFrom = timestamp; - const mergeTo = query.profileType().delta - ? mergeFrom + durationInMilliseconds - : mergeFrom; - selectProfile(new MergedProfileSelection(mergeFrom, mergeTo, query)); - }} - /> -
- ) : ( - <> - {profileSelection == null ? ( -
- -
- ) : null} - +
+
+ > + {showMetricsGraph ? 'Hide' : 'Show'} Metrics Graph + + {showMetricsGraph ? ( +
+ {querySelection.expression !== undefined && + querySelection.expression.length > 0 && + querySelection.from !== undefined && + querySelection.to !== undefined ? ( +
+ { + const from = range.getFromMs(); + const to = range.getToMs(); + let mergedProfileParams = {}; + if (query.profileType().delta) { + mergedProfileParams = {mergeFrom: from, mergeTo: to}; + } + setTimeRangeSelection(range); + selectQuery({ + expression: queryExpressionString, + from, + to, + timeSelection: range.getRangeKey(), + ...mergedProfileParams, + }); + }} + addLabelMatcher={addLabelMatcher} + onPointClick={( + timestamp: number, + labels: Label[], + queryExpression: string, + duration: number + ) => { + // TODO: Pass the query object via click rather than queryExpression + let query = Query.parse(queryExpression); + labels.forEach(l => { + const [newQuery, updated] = query.setMatcher(l.name, l.value); + if (updated) { + query = newQuery; + } + }); + + const durationInMilliseconds = duration / 1000000; // duration is in nanoseconds + const mergeFrom = timestamp; + const mergeTo = query.profileType().delta + ? mergeFrom + durationInMilliseconds + : mergeFrom; + selectProfile(new MergedProfileSelection(mergeFrom, mergeTo, query)); + }} + /> +
+ ) : ( + <> + {profileSelection == null ? ( +
+ +
+ ) : null} + + )} +
+ ) : null}
); diff --git a/ui/packages/shared/profile/src/ProfileSource.tsx b/ui/packages/shared/profile/src/ProfileSource.tsx index 760217d77df..0b6f8f3fa84 100644 --- a/ui/packages/shared/profile/src/ProfileSource.tsx +++ b/ui/packages/shared/profile/src/ProfileSource.tsx @@ -184,7 +184,7 @@ export class ProfileDiffSource implements ProfileSource { const bDesc = this.b.toString(); if (aDesc === bDesc) { - return 'profile comparison'; + return 'Profile comparison'; } return `${this.a.toString()} compared with ${this.b.toString()}`; @@ -264,6 +264,6 @@ export class MergedProfileSource implements ProfileSource { )}`; } - return `merged profiles${queryPart}${timePart}`; + return `Merged profiles${queryPart}${timePart}`; } } diff --git a/ui/packages/shared/profile/src/ProfileView/index.tsx b/ui/packages/shared/profile/src/ProfileView/index.tsx index ff1f128dfd7..ef7376768bd 100644 --- a/ui/packages/shared/profile/src/ProfileView/index.tsx +++ b/ui/packages/shared/profile/src/ProfileView/index.tsx @@ -337,28 +337,37 @@ export const ProfileView = ({ [groupBy, setGroupBy] ); + const showDivider = + hasProfileSource && + (profileViewExternalMainActions === null || profileViewExternalMainActions === undefined); + return ( + {showDivider ? ( + <> +
+ + ) : null}
{hasProfileSource && (
-
+
{headerParts.length > 0 ? headerParts[0].replace(/"/g, '') : ''}
-
+
{headerParts.length > 1 ? headerParts[headerParts.length - 1].replace(/"/g, '') : ''} @@ -415,10 +424,10 @@ export const ProfileView = ({ {...provided.draggableProps} key={dashboardItem} className={cx( - 'w-full rounded p-2 shadow dark:border dark:border-gray-700 dark:bg-gray-700 min-h-96', + 'w-full min-h-96', snapshot.isDragging ? 'bg-gray-200 dark:bg-gray-500' - : 'bg-white dark:bg-gray-700' + : 'bg-white dark:bg-gray-900' )} >