@parca/profile 0.16.252 → 0.16.254

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,14 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## 0.16.254 (2023-09-14)
7
+
8
+ **Note:** Version bump only for package @parca/profile
9
+
10
+ ## [0.16.253](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.252...@parca/profile@0.16.253) (2023-09-11)
11
+
12
+ **Note:** Version bump only for package @parca/profile
13
+
6
14
  ## 0.16.252 (2023-09-11)
7
15
 
8
16
  **Note:** Version bump only for package @parca/profile
@@ -1,6 +1,6 @@
1
1
  /// <reference types="react" />
2
2
  import { QueryServiceClient } from '@parca/client';
3
- import type { NavigateFunction } from '@parca/utilities';
3
+ import { type NavigateFunction } from '@parca/utilities';
4
4
  interface ProfileExplorerProps {
5
5
  queryClient: QueryServiceClient;
6
6
  queryParams: any;
@@ -1,4 +1,4 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  // Copyright 2022 The Parca Authors
3
3
  // Licensed under the Apache License, Version 2.0 (the "License");
4
4
  // you may not use this file except in compliance with the License.
@@ -15,10 +15,14 @@ import { useEffect, useState } from 'react';
15
15
  import { Provider } from 'react-redux';
16
16
  import { DateTimeRange, KeyDownProvider, useParcaContext } from '@parca/components';
17
17
  import { store } from '@parca/store';
18
+ import { capitalizeOnlyFirstLetter } from '@parca/utilities';
18
19
  import { ProfileSelectionFromParams, SuffixParams } from '..';
19
20
  import { useProfileTypes } from '../ProfileSelector';
20
21
  import ProfileExplorerCompare from './ProfileExplorerCompare';
21
22
  import ProfileExplorerSingle from './ProfileExplorerSingle';
23
+ const ErrorContent = ({ errorMessage }) => {
24
+ return (_jsx("div", { className: "relative rounded border border-red-400 bg-red-100 px-4 py-3 text-red-700", role: "alert", children: _jsx("span", { className: "block sm:inline", children: errorMessage }) }));
25
+ };
22
26
  const getExpressionAsAString = (expression) => {
23
27
  const x = Array.isArray(expression) ? expression.join() : expression;
24
28
  return x;
@@ -45,10 +49,10 @@ const swapQueryParameters = (o) => {
45
49
  };
46
50
  const ProfileExplorerApp = ({ queryClient, queryParams, navigateTo, }) => {
47
51
  const { loading: profileTypesLoading, data: profileTypesData, error: profileTypesError, } = useProfileTypes(queryClient);
48
- const { loader, noDataPrompt, onError } = useParcaContext();
52
+ const { loader, noDataPrompt, onError, authenticationErrorMessage } = useParcaContext();
49
53
  useEffect(() => {
50
54
  if (profileTypesError !== undefined && profileTypesError !== null) {
51
- onError?.(profileTypesError, 'ProfileExplorer');
55
+ onError?.(profileTypesError);
52
56
  }
53
57
  }, [profileTypesError, onError]);
54
58
  /* eslint-disable @typescript-eslint/naming-convention */
@@ -83,7 +87,10 @@ const ProfileExplorerApp = ({ queryClient, queryParams, navigateTo, }) => {
83
87
  return _jsx(_Fragment, { children: noDataPrompt });
84
88
  }
85
89
  if (profileTypesError !== undefined && profileTypesError !== null) {
86
- return (_jsxs("div", { className: "relative rounded border border-red-400 bg-red-100 px-4 py-3 text-red-700", role: "alert", children: [_jsx("strong", { className: "font-bold", children: "Error! " }), _jsx("span", { className: "block sm:inline", children: profileTypesError.message })] }));
90
+ if (authenticationErrorMessage !== undefined && profileTypesError.code === 'UNAUTHENTICATED') {
91
+ return _jsx(ErrorContent, { errorMessage: authenticationErrorMessage });
92
+ }
93
+ return _jsx(ErrorContent, { errorMessage: capitalizeOnlyFirstLetter(profileTypesError.message) });
87
94
  }
88
95
  const sanitizedRange = sanitizeDateRange(time_selection_a, from_a, to_a);
89
96
  time_selection_a = sanitizedRange.time_selection_a;
@@ -26,6 +26,7 @@ export const IcicleGraph = memo(function IcicleGraph({ graph, total, filtered, w
26
26
  const coloredGraph = useColoredGraph(graph);
27
27
  const currentSearchString = selectQueryParam('search_string') ?? '';
28
28
  const compareMode = selectQueryParam('compare_a') === 'true' && selectQueryParam('compare_b') === 'true';
29
+ const isColorStackLegendEnabled = selectQueryParam('color_stack_legend') === 'true';
29
30
  useEffect(() => {
30
31
  if (ref.current != null) {
31
32
  setHeight(ref?.current.getBoundingClientRect().height);
@@ -40,6 +41,6 @@ export const IcicleGraph = memo(function IcicleGraph({ graph, total, filtered, w
40
41
  if (coloredGraph.root === undefined || width === undefined) {
41
42
  return _jsx(_Fragment, {});
42
43
  }
43
- return (_jsxs("div", { onMouseLeave: () => dispatch(setHoveringNode(undefined)), children: [_jsx(ColorStackLegend, { navigateTo: navigateTo, compareMode: compareMode }), _jsx(GraphTooltip, { unit: sampleUnit, total: total, totalUnfiltered: total + filtered, contextElement: svg.current, strings: coloredGraph.stringTable, mappings: coloredGraph.mapping, locations: coloredGraph.locations, functions: coloredGraph.function }), _jsx("svg", { className: "font-robotoMono", width: width, height: height, preserveAspectRatio: "xMinYMid", ref: svg, children: _jsx("g", { ref: ref, children: _jsx("g", { transform: 'translate(0, 0)', children: _jsx(IcicleNode, { x: 0, y: 0, totalWidth: width, height: RowHeight, setCurPath: setCurPath, curPath: curPath, data: coloredGraph.root, strings: coloredGraph.stringTable, mappings: coloredGraph.mapping, locations: coloredGraph.locations, functions: coloredGraph.function, total: total, xScale: xScale, path: [], level: 0, isRoot: true, searchString: currentSearchString, compareMode: compareMode }) }) }) })] }));
44
+ return (_jsxs("div", { onMouseLeave: () => dispatch(setHoveringNode(undefined)), children: [isColorStackLegendEnabled && (_jsx(ColorStackLegend, { navigateTo: navigateTo, compareMode: compareMode })), _jsx(GraphTooltip, { unit: sampleUnit, total: total, totalUnfiltered: total + filtered, contextElement: svg.current, strings: coloredGraph.stringTable, mappings: coloredGraph.mapping, locations: coloredGraph.locations, functions: coloredGraph.function }), _jsx("svg", { className: "font-robotoMono", width: width, height: height, preserveAspectRatio: "xMinYMid", ref: svg, children: _jsx("g", { ref: ref, children: _jsx("g", { transform: 'translate(0, 0)', children: _jsx(IcicleNode, { x: 0, y: 0, totalWidth: width, height: RowHeight, setCurPath: setCurPath, curPath: curPath, data: coloredGraph.root, strings: coloredGraph.stringTable, mappings: coloredGraph.mapping, locations: coloredGraph.locations, functions: coloredGraph.function, total: total, xScale: xScale, path: [], level: 0, isRoot: true, searchString: currentSearchString, compareMode: compareMode }) }) }) })] }));
44
45
  });
45
46
  export default IcicleGraph;
@@ -40,10 +40,10 @@ const ColorStackLegend = ({ mappingColors, navigateTo, compareMode = false, }) =
40
40
  if (colorProfileName === 'default' || compareMode) {
41
41
  return _jsx(_Fragment, {});
42
42
  }
43
- return (_jsx("div", { className: "my-6 flex flex-wrap justify-center gap-2", children: stackColorArray.map(([feature, color]) => {
43
+ return (_jsx("div", { className: "my-4 flex w-full flex-wrap justify-start", children: stackColorArray.map(([feature, color]) => {
44
44
  const filteringAllowed = feature !== EVERYTHING_ELSE;
45
45
  const isHighlighted = currentSearchString === feature;
46
- return (_jsxs("div", { className: cx('flex items-center justify-between gap-1 text-ellipsis p-1 pr-2', {
46
+ return (_jsxs("div", { className: cx('flex-no-wrap mb-1 flex w-1/5 items-center justify-between text-ellipsis p-1', {
47
47
  'cursor-pointer': filteringAllowed,
48
48
  'bg-gray-200 dark:bg-gray-800': isHighlighted,
49
49
  }), onClick: () => {
@@ -55,10 +55,10 @@ const ColorStackLegend = ({ mappingColors, navigateTo, compareMode = false, }) =
55
55
  return;
56
56
  }
57
57
  setSearchString(feature);
58
- }, children: [_jsxs("div", { className: "flex items-center", children: [_jsx("div", { className: "mr-1 inline-block h-4 w-4", style: { backgroundColor: color } }), _jsx("span", { className: "text-sm", children: feature })] }), isHighlighted ? (_jsx(Icon, { icon: "radix-icons:cross-circled", onClick: e => {
59
- setSearchString('');
60
- e.stopPropagation();
61
- } })) : null] }, feature));
58
+ }, children: [_jsxs("div", { className: "flex w-11/12 items-center justify-start", children: [_jsx("div", { className: "flex w-5 items-center", children: _jsx("div", { className: "mr-1 inline-block h-4 w-4", style: { backgroundColor: color } }) }), _jsx("div", { className: "shrink overflow-hidden text-ellipsis whitespace-nowrap text-sm hover:whitespace-normal", children: feature })] }), _jsx("div", { className: "flex w-1/12 justify-end", children: isHighlighted && (_jsx(Icon, { icon: "radix-icons:cross-circled", onClick: e => {
59
+ setSearchString('');
60
+ e.stopPropagation();
61
+ } })) })] }, feature));
62
62
  }) }));
63
63
  };
64
64
  export default ColorStackLegend;
@@ -49,6 +49,7 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({ arrow, total, f
49
49
  const ref = useRef(null);
50
50
  const currentSearchString = selectQueryParam('search_string') ?? '';
51
51
  const compareMode = selectQueryParam('compare_a') === 'true' && selectQueryParam('compare_b') === 'true';
52
+ const isColorStackLegendEnabled = selectQueryParam('color_stack_legend') === 'true';
52
53
  const mappings = useMemo(() => {
53
54
  // Read the mappings from the dictionary that contains all mapping strings.
54
55
  // This is great, as might only have a dozen or so mappings,
@@ -135,6 +136,6 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({ arrow, total, f
135
136
  if (table.numRows === 0 || width === undefined) {
136
137
  return _jsx(_Fragment, {});
137
138
  }
138
- return (_jsxs("div", { onMouseLeave: () => dispatch(setHoveringNode(undefined)), children: [_jsx(ColorStackLegend, { mappingColors: mappingColors, navigateTo: navigateTo, compareMode: compareMode }), dockedMetainfo ? (_jsx(DockedGraphTooltip, { table: table, row: hoveringRow, level: hoveringLevel ?? 0, total: total, totalUnfiltered: total + filtered, unit: sampleUnit })) : (_jsx(GraphTooltipArrow, { contextElement: svg.current, children: _jsx(GraphTooltipArrowContent, { table: table, row: hoveringRow, level: hoveringLevel ?? 0, isFixed: false, total: total, totalUnfiltered: total + filtered, unit: sampleUnit, navigateTo: navigateTo }) })), root] }));
139
+ return (_jsxs("div", { onMouseLeave: () => dispatch(setHoveringNode(undefined)), children: [isColorStackLegendEnabled && (_jsx(ColorStackLegend, { mappingColors: mappingColors, navigateTo: navigateTo, compareMode: compareMode })), dockedMetainfo ? (_jsx(DockedGraphTooltip, { table: table, row: hoveringRow, level: hoveringLevel ?? 0, total: total, totalUnfiltered: total + filtered, unit: sampleUnit })) : (_jsx(GraphTooltipArrow, { contextElement: svg.current, children: _jsx(GraphTooltipArrowContent, { table: table, row: hoveringRow, level: hoveringLevel ?? 0, isFixed: false, total: total, totalUnfiltered: total + filtered, unit: sampleUnit, navigateTo: navigateTo }) })), root] }));
139
140
  });
140
141
  export default IcicleGraphArrow;
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  // Copyright 2022 The Parca Authors
3
3
  // Licensed under the Apache License, Version 2.0 (the "License");
4
4
  // you may not use this file except in compliance with the License.
@@ -15,11 +15,28 @@ import { Fragment, useCallback, useEffect, useMemo } from 'react';
15
15
  import { Menu, Transition } from '@headlessui/react';
16
16
  import { Icon } from '@iconify/react';
17
17
  import { Button, Select, useParcaContext, useURLState } from '@parca/components';
18
- import { divide, selectQueryParam } from '@parca/utilities';
18
+ import { USER_PREFERENCES, useUserPreference } from '@parca/hooks';
19
+ import { capitalizeOnlyFirstLetter, divide, selectQueryParam, } from '@parca/utilities';
19
20
  import DiffLegend from '../components/DiffLegend';
20
21
  import IcicleGraph from './IcicleGraph';
21
22
  import IcicleGraphArrow, { FIELD_CUMULATIVE, FIELD_DIFF, FIELD_FUNCTION_NAME, FIELD_LABELS, } from './IcicleGraphArrow';
22
23
  const numberFormatter = new Intl.NumberFormat('en-US');
24
+ const ErrorContent = ({ errorMessage }) => {
25
+ return _jsx("div", { className: "flex justify-center p-10", children: errorMessage });
26
+ };
27
+ const ShowHideLegendButton = ({ navigateTo }) => {
28
+ const [colorStackLegend, setStoreColorStackLegend] = useURLState({
29
+ param: 'color_stack_legend',
30
+ navigateTo,
31
+ });
32
+ const compareMode = selectQueryParam('compare_a') === 'true' && selectQueryParam('compare_b') === 'true';
33
+ const isColorStackLegendEnabled = colorStackLegend === 'true';
34
+ const [colorProfileName] = useUserPreference(USER_PREFERENCES.FLAMEGRAPH_COLOR_PROFILE.key);
35
+ const setColorStackLegend = useCallback((value) => {
36
+ setStoreColorStackLegend(value);
37
+ }, [setStoreColorStackLegend]);
38
+ return (_jsx(_Fragment, { children: colorProfileName === 'default' || compareMode ? null : (_jsxs(Button, { className: "gap-2", variant: "neutral", onClick: () => setColorStackLegend(isColorStackLegendEnabled ? 'false' : 'true'), children: [isColorStackLegendEnabled ? 'Hide legend' : 'Show legend', _jsx(Icon, { icon: isColorStackLegendEnabled ? 'ph:eye-closed' : 'ph:eye', width: 20 })] })) }));
39
+ };
23
40
  const GroupAndSortActionButtons = ({ navigateTo }) => {
24
41
  const [storeSortBy = FIELD_FUNCTION_NAME, setStoreSortBy] = useURLState({
25
42
  param: 'sort_by',
@@ -50,7 +67,7 @@ const GroupAndSortActionButtons = ({ navigateTo }) => {
50
67
  return (_jsxs(_Fragment, { children: [_jsx(GroupByDropdown, { groupBy: groupBy, toggleGroupBy: toggleGroupBy }), _jsx(SortBySelect, { compareMode: compareMode, sortBy: storeSortBy, setSortBy: setStoreSortBy })] }));
51
68
  };
52
69
  const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, total, filtered, curPath, setNewCurPath, sampleUnit, navigateTo, loading, setActionButtons, error, width, }) {
53
- const { loader } = useParcaContext();
70
+ const { loader, onError, authenticationErrorMessage } = useParcaContext();
54
71
  const compareMode = selectQueryParam('compare_a') === 'true' && selectQueryParam('compare_b') === 'true';
55
72
  const [storeSortBy = FIELD_FUNCTION_NAME] = useURLState({
56
73
  param: 'sort_by',
@@ -78,14 +95,17 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, to
78
95
  if (setActionButtons === undefined) {
79
96
  return;
80
97
  }
81
- setActionButtons(_jsx("div", { className: "flex w-full justify-end gap-2 pb-2", children: _jsxs("div", { className: "ml-2 flex w-full items-end justify-between gap-2", children: [arrow !== undefined && _jsx(GroupAndSortActionButtons, { navigateTo: navigateTo }), _jsx("div", { children: _jsx(Button, { color: "neutral", onClick: () => setNewCurPath([]), disabled: curPath.length === 0, variant: "neutral", children: "Reset View" }) })] }) }));
98
+ setActionButtons(_jsx("div", { className: "flex w-full justify-end gap-2 pb-2", children: _jsxs("div", { className: "ml-2 flex w-full items-end justify-between gap-2", children: [arrow !== undefined && _jsx(GroupAndSortActionButtons, { navigateTo: navigateTo }), _jsx(ShowHideLegendButton, { navigateTo: navigateTo }), _jsx(Button, { variant: "neutral", onClick: () => setNewCurPath([]), disabled: curPath.length === 0, children: "Reset View" })] }) }));
82
99
  }, [navigateTo, arrow, curPath, setNewCurPath, setActionButtons]);
83
100
  if (loading) {
84
101
  return _jsx("div", { className: "h-96", children: loader });
85
102
  }
86
103
  if (error != null) {
87
- console.error('Error: ', error);
88
- return _jsxs("div", { className: "flex justify-center p-10", children: ["An error occurred: ", error.message] });
104
+ onError?.(error);
105
+ if (authenticationErrorMessage !== undefined && error.code === 'UNAUTHENTICATED') {
106
+ return _jsx(ErrorContent, { errorMessage: authenticationErrorMessage });
107
+ }
108
+ return _jsx(ErrorContent, { errorMessage: capitalizeOnlyFirstLetter(error.message) });
89
109
  }
90
110
  if (graph === undefined && arrow === undefined)
91
111
  return _jsx("div", { className: "mx-auto text-center", children: "No data..." });
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  // Copyright 2022 The Parca Authors
3
3
  // Licensed under the Apache License, Version 2.0 (the "License");
4
4
  // you may not use this file except in compliance with the License.
@@ -15,10 +15,13 @@ import { useEffect, useState } from 'react';
15
15
  import { Duration, Timestamp } from '@parca/client';
16
16
  import { useGrpcMetadata, useParcaContext } from '@parca/components';
17
17
  import { Query } from '@parca/parser';
18
- import { getStepDuration } from '@parca/utilities';
18
+ import { capitalizeOnlyFirstLetter, getStepDuration } from '@parca/utilities';
19
19
  import MetricsGraph from '../MetricsGraph';
20
20
  import { useMetricsGraphDimensions } from '../MetricsGraph/useMetricsGraphDimensions';
21
21
  import useDelayedLoader from '../useDelayedLoader';
22
+ const ErrorContent = ({ errorMessage }) => {
23
+ return (_jsx("div", { className: "relative rounded border border-red-400 bg-red-100 px-4 py-3 text-red-700", role: "alert", children: _jsx("span", { className: "block sm:inline", children: errorMessage }) }));
24
+ };
22
25
  export const ProfileMetricsEmptyState = ({ message }) => {
23
26
  return (_jsx("div", { className: "flex h-full w-full flex-col items-center justify-center", children: _jsx("p", { children: message }) }));
24
27
  };
@@ -58,11 +61,11 @@ export const useQueryRange = (client, queryExpression, start, end) => {
58
61
  const ProfileMetricsGraph = ({ queryClient, queryExpression, profile, from, to, setTimeRange, addLabelMatcher, onPointClick, comparing = false, }) => {
59
62
  const { isLoading, response, error } = useQueryRange(queryClient, queryExpression, from, to);
60
63
  const isLoaderVisible = useDelayedLoader(isLoading);
61
- const { loader, onError, perf } = useParcaContext();
64
+ const { loader, onError, perf, authenticationErrorMessage } = useParcaContext();
62
65
  const { width, height, margin } = useMetricsGraphDimensions(comparing);
63
66
  useEffect(() => {
64
67
  if (error !== null) {
65
- onError?.(error, 'metricsGraph');
68
+ onError?.(error);
66
69
  }
67
70
  }, [error, onError]);
68
71
  useEffect(() => {
@@ -77,7 +80,10 @@ const ProfileMetricsGraph = ({ queryClient, queryExpression, profile, from, to,
77
80
  return _jsx(_Fragment, { children: loader });
78
81
  }
79
82
  if (error !== null) {
80
- return (_jsxs("div", { className: "relative rounded border border-red-400 bg-red-100 px-4 py-3 text-red-700", role: "alert", children: [_jsx("strong", { className: "font-bold", children: "Error! " }), _jsx("span", { className: "block sm:inline", children: error.message })] }));
83
+ if (authenticationErrorMessage !== undefined && error.code === 'UNAUTHENTICATED') {
84
+ return _jsx(ErrorContent, { errorMessage: authenticationErrorMessage });
85
+ }
86
+ return _jsx(ErrorContent, { errorMessage: capitalizeOnlyFirstLetter(error.message) });
81
87
  }
82
88
  if (dataAvailable) {
83
89
  const handleSampleClick = (timestamp, _value, labels) => {
package/dist/styles.css CHANGED
@@ -1 +1 @@
1
- /*! tailwindcss v3.2.4 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}html{-webkit-text-size-adjust:100%;font-feature-settings:normal;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.-inset-2{bottom:-.5rem;left:-.5rem;right:-.5rem;top:-.5rem}.inset-y-0{bottom:0;top:0}.left-\[25px\]{left:25px}.left-0{left:0}.top-\[-46px\]{top:-46px}.right-0{right:0}.bottom-0{bottom:0}.z-50{z-index:50}.z-10{z-index:10}.z-20{z-index:20}.m-auto{margin:auto}.m-2{margin:.5rem}.mx-auto{margin-left:auto;margin-right:auto}.mx-2{margin-left:.5rem;margin-right:.5rem}.my-2{margin-bottom:.5rem;margin-top:.5rem}.my-20{margin-bottom:5rem;margin-top:5rem}.my-6{margin-bottom:1.5rem;margin-top:1.5rem}.mr-3{margin-right:.75rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mr-6{margin-right:1.5rem}.mr-1{margin-right:.25rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-8{margin-top:2rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-fit{height:-moz-fit-content;height:fit-content}.h-10{height:2.5rem}.h-96{height:24rem}.h-6{height:1.5rem}.h-4{height:1rem}.h-full{height:100%}.h-1{height:.25rem}.h-\[80vh\]{height:80vh}.h-5{height:1.25rem}.max-h-\[400px\]{max-height:400px}.min-h-52{min-height:13rem}.min-h-48{min-height:12rem}.min-h-\[200px\]{min-height:200px}.w-full{width:100%}.w-auto{width:auto}.w-1\/4{width:25%}.w-3\/4{width:75%}.w-\[500px\]{width:500px}.w-4{width:1rem}.w-40{width:10rem}.w-3{width:.75rem}.w-5{width:1.25rem}.w-7{width:1.75rem}.w-9{width:2.25rem}.w-11{width:2.75rem}.w-\[52px\]{width:52px}.w-\[68px\]{width:68px}.w-\[76px\]{width:76px}.w-\[84px\]{width:84px}.w-\[92px\]{width:92px}.w-\[100px\]{width:100px}.w-\[108px\]{width:108px}.w-\[116px\]{width:116px}.w-8{width:2rem}.w-44{width:11rem}.w-\[460px\]{width:460px}.w-16{width:4rem}.w-fit{width:-moz-fit-content;width:fit-content}.w-\[420px\]{width:420px}.min-w-\[300px\]{min-width:300px}.min-w-\[400px\]{min-width:400px}.max-w-\[500px\]{max-width:500px}.max-w-\[300px\]{max-width:300px}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.flex-grow-0{flex-grow:0}.flex-grow{flex-grow:1}.table-auto{table-layout:auto}.table-fixed{table-layout:fixed}.translate-y-1{--tw-translate-y:0.25rem}.translate-y-0,.translate-y-1{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-0{--tw-translate-y:0px}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.cursor-default{cursor:default}.cursor-not-allowed{cursor:not-allowed}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.content-start{align-content:flex-start}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-4{gap:1rem}.gap-2{gap:.5rem}.gap-1{gap:.25rem}.gap-3{gap:.75rem}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1.25rem*var(--tw-space-y-reverse));margin-top:calc(1.25rem*(1 - var(--tw-space-y-reverse)))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.overflow-x-hidden{overflow-x:hidden}.text-ellipsis{text-overflow:ellipsis}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-none{border-radius:0}.rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.rounded-l{border-bottom-left-radius:.25rem;border-top-left-radius:.25rem}.rounded-r{border-bottom-right-radius:.25rem;border-top-right-radius:.25rem}.border{border-width:1px}.border-r{border-right-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-r-0{border-right-width:0}.border-l-0{border-left-width:0}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.border-red-400{--tw-border-opacity:1;border-color:rgb(248 113 113/var(--tw-border-opacity))}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity))}.border-gray-400{--tw-border-opacity:1;border-color:rgb(156 163 175/var(--tw-border-opacity))}.border-r-gray-200{--tw-border-opacity:1;border-right-color:rgb(229 231 235/var(--tw-border-opacity))}.border-l-amber-900{--tw-border-opacity:1;border-left-color:rgb(120 53 15/var(--tw-border-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-indigo-600{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(254 226 226/var(--tw-bg-opacity))}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}.bg-yellow-200{--tw-bg-opacity:1;background-color:rgb(254 240 138/var(--tw-bg-opacity))}.bg-yellow-700{--tw-bg-opacity:1;background-color:rgb(161 98 7/var(--tw-bg-opacity))}.bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.bg-inherit{background-color:inherit}.bg-opacity-90{--tw-bg-opacity:0.9}.fill-transparent{fill:transparent}.fill-current{fill:currentColor}.stroke-gray-300{stroke:#d1d5db}.stroke-white{stroke:#fff}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-10{padding:2.5rem}.p-4{padding:1rem}.p-1{padding:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-4{padding-bottom:1rem;padding-top:1rem}.px-4{padding-left:1rem;padding-right:1rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-0{padding-bottom:0;padding-top:0}.px-1{padding-left:.25rem;padding-right:.25rem}.px-8{padding-left:2rem;padding-right:2rem}.pr-0{padding-right:0}.pl-3{padding-left:.75rem}.pr-9{padding-right:2.25rem}.pt-2{padding-top:.5rem}.pb-4{padding-bottom:1rem}.pb-2{padding-bottom:.5rem}.pr-10{padding-right:2.5rem}.pr-2{padding-right:.5rem}.pl-1{padding-left:.25rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.text-base{font-size:1rem;line-height:1.5rem}.text-xl{font-size:1.25rem}.text-lg,.text-xl{line-height:1.75rem}.text-lg{font-size:1.125rem}.font-semibold{font-weight:600}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.capitalize{text-transform:capitalize}.leading-6{line-height:1.5rem}.leading-5{line-height:1.25rem}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.text-indigo-600{--tw-text-opacity:1;color:rgb(79 70 229/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.\!text-indigo-600{--tw-text-opacity:1!important;color:rgb(79 70 229/var(--tw-text-opacity))!important}.opacity-100{opacity:1}.opacity-0{opacity:0}.opacity-90{opacity:.9}.opacity-50{opacity:.5}.shadow-\[0_0_10px_2px_rgba\(0\2c 0\2c 0\2c 0\.3\)\]{--tw-shadow:0 0 10px 2px rgba(0,0,0,.3);--tw-shadow-colored:0 0 10px 2px var(--tw-shadow-color)}.shadow-\[0_0_10px_2px_rgba\(0\2c 0\2c 0\2c 0\.3\)\],.shadow-lg{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-black{--tw-ring-opacity:1;--tw-ring-color:rgb(0 0 0/var(--tw-ring-opacity))}.ring-opacity-5{--tw-ring-opacity:0.05}.blur{--tw-blur:blur(8px)}.blur,.invert{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.invert{--tw-invert:invert(100%)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-100{transition-duration:.1s}.duration-200{transition-duration:.2s}.duration-150{transition-duration:.15s}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.focus\:border-indigo-500:focus{--tw-border-opacity:1;border-color:rgb(99 102 241/var(--tw-border-opacity))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-indigo-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(99 102 241/var(--tw-ring-opacity))}.focus\:ring-indigo-600:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(79 70 229/var(--tw-ring-opacity))}.group:hover .group-hover\:flex{display:flex}[class~=theme-dark] .dark\:border{border-width:1px}[class~=theme-dark] .dark\:border-gray-500{--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}[class~=theme-dark] .dark\:border-gray-600{--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity))}[class~=theme-dark] .dark\:border-gray-700{--tw-border-opacity:1;border-color:rgb(55 65 81/var(--tw-border-opacity))}[class~=theme-dark] .dark\:border-r-gray-700{--tw-border-opacity:1;border-right-color:rgb(55 65 81/var(--tw-border-opacity))}[class~=theme-dark] .dark\:bg-gray-900{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-500{--tw-bg-opacity:1;background-color:rgb(107 114 128/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-yellow-700{--tw-bg-opacity:1;background-color:rgb(161 98 7/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-opacity-80{--tw-bg-opacity:0.8}[class~=theme-dark] .dark\:stroke-gray-500{stroke:#6b7280}[class~=theme-dark] .dark\:stroke-gray-700{stroke:#374151}[class~=theme-dark] .dark\:text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-gray-50{--tw-text-opacity:1;color:rgb(249 250 251/var(--tw-text-opacity))}[class~=theme-dark] .dark\:\!text-indigo-400{--tw-text-opacity:1!important;color:rgb(129 140 248/var(--tw-text-opacity))!important}[class~=theme-dark] .dark\:ring-white{--tw-ring-opacity:1;--tw-ring-color:rgb(255 255 255/var(--tw-ring-opacity))}[class~=theme-dark] .dark\:ring-opacity-20{--tw-ring-opacity:0.2}@media (min-width:640px){.sm\:inline{display:inline}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}
1
+ /*! tailwindcss v3.2.4 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}html{-webkit-text-size-adjust:100%;font-feature-settings:normal;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.-inset-2{bottom:-.5rem;left:-.5rem;right:-.5rem;top:-.5rem}.inset-y-0{bottom:0;top:0}.left-\[25px\]{left:25px}.left-0{left:0}.top-\[-46px\]{top:-46px}.right-0{right:0}.bottom-0{bottom:0}.z-50{z-index:50}.z-10{z-index:10}.z-20{z-index:20}.m-auto{margin:auto}.m-2{margin:.5rem}.mx-auto{margin-left:auto;margin-right:auto}.mx-2{margin-left:.5rem;margin-right:.5rem}.my-2{margin-bottom:.5rem;margin-top:.5rem}.my-20{margin-bottom:5rem;margin-top:5rem}.my-6{margin-bottom:1.5rem;margin-top:1.5rem}.my-4{margin-bottom:1rem;margin-top:1rem}.mr-3{margin-right:.75rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mr-6{margin-right:1.5rem}.mr-1{margin-right:.25rem}.mb-1{margin-bottom:.25rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-8{margin-top:2rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-fit{height:-moz-fit-content;height:fit-content}.h-10{height:2.5rem}.h-96{height:24rem}.h-6{height:1.5rem}.h-4{height:1rem}.h-full{height:100%}.h-1{height:.25rem}.h-\[80vh\]{height:80vh}.h-5{height:1.25rem}.max-h-\[400px\]{max-height:400px}.min-h-52{min-height:13rem}.min-h-48{min-height:12rem}.min-h-\[200px\]{min-height:200px}.w-full{width:100%}.w-auto{width:auto}.w-1\/4{width:25%}.w-3\/4{width:75%}.w-\[500px\]{width:500px}.w-4{width:1rem}.w-40{width:10rem}.w-3{width:.75rem}.w-5{width:1.25rem}.w-7{width:1.75rem}.w-9{width:2.25rem}.w-11{width:2.75rem}.w-\[52px\]{width:52px}.w-\[68px\]{width:68px}.w-\[76px\]{width:76px}.w-\[84px\]{width:84px}.w-\[92px\]{width:92px}.w-\[100px\]{width:100px}.w-\[108px\]{width:108px}.w-\[116px\]{width:116px}.w-8{width:2rem}.w-44{width:11rem}.w-\[460px\]{width:460px}.w-1\/5{width:20%}.w-11\/12{width:91.666667%}.w-1\/12{width:8.333333%}.w-16{width:4rem}.w-fit{width:-moz-fit-content;width:fit-content}.w-\[420px\]{width:420px}.min-w-\[300px\]{min-width:300px}.min-w-\[400px\]{min-width:400px}.max-w-\[500px\]{max-width:500px}.max-w-\[300px\]{max-width:300px}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.flex-grow-0{flex-grow:0}.flex-grow{flex-grow:1}.table-auto{table-layout:auto}.table-fixed{table-layout:fixed}.translate-y-1{--tw-translate-y:0.25rem}.translate-y-0,.translate-y-1{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-0{--tw-translate-y:0px}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.cursor-default{cursor:default}.cursor-not-allowed{cursor:not-allowed}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.content-start{align-content:flex-start}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-4{gap:1rem}.gap-2{gap:.5rem}.gap-1{gap:.25rem}.gap-3{gap:.75rem}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1.25rem*var(--tw-space-y-reverse));margin-top:calc(1.25rem*(1 - var(--tw-space-y-reverse)))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.overflow-x-hidden{overflow-x:hidden}.text-ellipsis{text-overflow:ellipsis}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-none{border-radius:0}.rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.rounded-l{border-bottom-left-radius:.25rem;border-top-left-radius:.25rem}.rounded-r{border-bottom-right-radius:.25rem;border-top-right-radius:.25rem}.border{border-width:1px}.border-r{border-right-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-r-0{border-right-width:0}.border-l-0{border-left-width:0}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.border-red-400{--tw-border-opacity:1;border-color:rgb(248 113 113/var(--tw-border-opacity))}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity))}.border-gray-400{--tw-border-opacity:1;border-color:rgb(156 163 175/var(--tw-border-opacity))}.border-r-gray-200{--tw-border-opacity:1;border-right-color:rgb(229 231 235/var(--tw-border-opacity))}.border-l-amber-900{--tw-border-opacity:1;border-left-color:rgb(120 53 15/var(--tw-border-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-indigo-600{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(254 226 226/var(--tw-bg-opacity))}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}.bg-yellow-200{--tw-bg-opacity:1;background-color:rgb(254 240 138/var(--tw-bg-opacity))}.bg-yellow-700{--tw-bg-opacity:1;background-color:rgb(161 98 7/var(--tw-bg-opacity))}.bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.bg-inherit{background-color:inherit}.bg-opacity-90{--tw-bg-opacity:0.9}.fill-transparent{fill:transparent}.fill-current{fill:currentColor}.stroke-gray-300{stroke:#d1d5db}.stroke-white{stroke:#fff}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-10{padding:2.5rem}.p-4{padding:1rem}.p-1{padding:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-4{padding-bottom:1rem;padding-top:1rem}.px-4{padding-left:1rem;padding-right:1rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-0{padding-bottom:0;padding-top:0}.px-1{padding-left:.25rem;padding-right:.25rem}.px-8{padding-left:2rem;padding-right:2rem}.pr-0{padding-right:0}.pl-3{padding-left:.75rem}.pr-9{padding-right:2.25rem}.pt-2{padding-top:.5rem}.pb-4{padding-bottom:1rem}.pb-2{padding-bottom:.5rem}.pr-10{padding-right:2.5rem}.pr-2{padding-right:.5rem}.pl-1{padding-left:.25rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.text-base{font-size:1rem;line-height:1.5rem}.text-xl{font-size:1.25rem}.text-lg,.text-xl{line-height:1.75rem}.text-lg{font-size:1.125rem}.font-semibold{font-weight:600}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.capitalize{text-transform:capitalize}.leading-6{line-height:1.5rem}.leading-5{line-height:1.25rem}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.text-indigo-600{--tw-text-opacity:1;color:rgb(79 70 229/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.\!text-indigo-600{--tw-text-opacity:1!important;color:rgb(79 70 229/var(--tw-text-opacity))!important}.opacity-100{opacity:1}.opacity-0{opacity:0}.opacity-90{opacity:.9}.opacity-50{opacity:.5}.shadow-\[0_0_10px_2px_rgba\(0\2c 0\2c 0\2c 0\.3\)\]{--tw-shadow:0 0 10px 2px rgba(0,0,0,.3);--tw-shadow-colored:0 0 10px 2px var(--tw-shadow-color)}.shadow-\[0_0_10px_2px_rgba\(0\2c 0\2c 0\2c 0\.3\)\],.shadow-lg{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-black{--tw-ring-opacity:1;--tw-ring-color:rgb(0 0 0/var(--tw-ring-opacity))}.ring-opacity-5{--tw-ring-opacity:0.05}.blur{--tw-blur:blur(8px)}.blur,.invert{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.invert{--tw-invert:invert(100%)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-100{transition-duration:.1s}.duration-200{transition-duration:.2s}.duration-150{transition-duration:.15s}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.hover\:whitespace-normal:hover{white-space:normal}.focus\:border-indigo-500:focus{--tw-border-opacity:1;border-color:rgb(99 102 241/var(--tw-border-opacity))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-indigo-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(99 102 241/var(--tw-ring-opacity))}.focus\:ring-indigo-600:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(79 70 229/var(--tw-ring-opacity))}.group:hover .group-hover\:flex{display:flex}[class~=theme-dark] .dark\:border{border-width:1px}[class~=theme-dark] .dark\:border-gray-500{--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}[class~=theme-dark] .dark\:border-gray-600{--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity))}[class~=theme-dark] .dark\:border-gray-700{--tw-border-opacity:1;border-color:rgb(55 65 81/var(--tw-border-opacity))}[class~=theme-dark] .dark\:border-r-gray-700{--tw-border-opacity:1;border-right-color:rgb(55 65 81/var(--tw-border-opacity))}[class~=theme-dark] .dark\:bg-gray-900{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-500{--tw-bg-opacity:1;background-color:rgb(107 114 128/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-yellow-700{--tw-bg-opacity:1;background-color:rgb(161 98 7/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-opacity-80{--tw-bg-opacity:0.8}[class~=theme-dark] .dark\:stroke-gray-500{stroke:#6b7280}[class~=theme-dark] .dark\:stroke-gray-700{stroke:#374151}[class~=theme-dark] .dark\:text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-gray-50{--tw-text-opacity:1;color:rgb(249 250 251/var(--tw-text-opacity))}[class~=theme-dark] .dark\:\!text-indigo-400{--tw-text-opacity:1!important;color:rgb(129 140 248/var(--tw-text-opacity))!important}[class~=theme-dark] .dark\:ring-white{--tw-ring-opacity:1;--tw-ring-color:rgb(255 255 255/var(--tw-ring-opacity))}[class~=theme-dark] .dark\:ring-opacity-20{--tw-ring-opacity:0.2}@media (min-width:640px){.sm\:inline{display:inline}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@parca/profile",
3
- "version": "0.16.252",
3
+ "version": "0.16.254",
4
4
  "description": "Profile viewing libraries",
5
5
  "dependencies": {
6
6
  "@parca/client": "^0.16.86",
7
- "@parca/components": "^0.16.190",
7
+ "@parca/components": "^0.16.192",
8
8
  "@parca/dynamicsize": "^0.16.54",
9
- "@parca/hooks": "^0.0.22",
9
+ "@parca/hooks": "^0.0.23",
10
10
  "@parca/parser": "^0.16.55",
11
- "@parca/store": "^0.16.101",
12
- "@parca/utilities": "^0.0.29",
11
+ "@parca/store": "^0.16.103",
12
+ "@parca/utilities": "^0.0.31",
13
13
  "@tanstack/react-query": "^4.0.5",
14
14
  "@types/react-beautiful-dnd": "^13.1.3",
15
15
  "apache-arrow": "^12.0.0",
@@ -49,5 +49,5 @@
49
49
  "access": "public",
50
50
  "registry": "https://registry.npmjs.org/"
51
51
  },
52
- "gitHead": "800a6bb7fe26d22a8e8d590f725f77e4d1d0a84d"
52
+ "gitHead": "0ca31b9ddf1c2db1ccd5cd3c5e7d7f0e572299fd"
53
53
  }
@@ -18,7 +18,7 @@ import {Provider} from 'react-redux';
18
18
  import {QueryServiceClient} from '@parca/client';
19
19
  import {DateTimeRange, KeyDownProvider, useParcaContext} from '@parca/components';
20
20
  import {store} from '@parca/store';
21
- import type {NavigateFunction} from '@parca/utilities';
21
+ import {capitalizeOnlyFirstLetter, type NavigateFunction} from '@parca/utilities';
22
22
 
23
23
  import {ProfileSelection, ProfileSelectionFromParams, SuffixParams} from '..';
24
24
  import {QuerySelection, useProfileTypes} from '../ProfileSelector';
@@ -31,6 +31,17 @@ interface ProfileExplorerProps {
31
31
  navigateTo: NavigateFunction;
32
32
  }
33
33
 
34
+ const ErrorContent = ({errorMessage}: {errorMessage: string}): JSX.Element => {
35
+ return (
36
+ <div
37
+ className="relative rounded border border-red-400 bg-red-100 px-4 py-3 text-red-700"
38
+ role="alert"
39
+ >
40
+ <span className="block sm:inline">{errorMessage}</span>
41
+ </div>
42
+ );
43
+ };
44
+
34
45
  const getExpressionAsAString = (expression: string | []): string => {
35
46
  const x = Array.isArray(expression) ? expression.join() : expression;
36
47
  return x;
@@ -81,11 +92,11 @@ const ProfileExplorerApp = ({
81
92
  error: profileTypesError,
82
93
  } = useProfileTypes(queryClient);
83
94
 
84
- const {loader, noDataPrompt, onError} = useParcaContext();
95
+ const {loader, noDataPrompt, onError, authenticationErrorMessage} = useParcaContext();
85
96
 
86
97
  useEffect(() => {
87
98
  if (profileTypesError !== undefined && profileTypesError !== null) {
88
- onError?.(profileTypesError, 'ProfileExplorer');
99
+ onError?.(profileTypesError);
89
100
  }
90
101
  }, [profileTypesError, onError]);
91
102
 
@@ -166,15 +177,11 @@ const ProfileExplorerApp = ({
166
177
  }
167
178
 
168
179
  if (profileTypesError !== undefined && profileTypesError !== null) {
169
- return (
170
- <div
171
- className="relative rounded border border-red-400 bg-red-100 px-4 py-3 text-red-700"
172
- role="alert"
173
- >
174
- <strong className="font-bold">Error! </strong>
175
- <span className="block sm:inline">{profileTypesError.message}</span>
176
- </div>
177
- );
180
+ if (authenticationErrorMessage !== undefined && profileTypesError.code === 'UNAUTHENTICATED') {
181
+ return <ErrorContent errorMessage={authenticationErrorMessage} />;
182
+ }
183
+
184
+ return <ErrorContent errorMessage={capitalizeOnlyFirstLetter(profileTypesError.message)} />;
178
185
  }
179
186
 
180
187
  const sanitizedRange = sanitizeDateRange(time_selection_a, from_a, to_a);
@@ -52,6 +52,7 @@ export const IcicleGraph = memo(function IcicleGraph({
52
52
  const currentSearchString = (selectQueryParam('search_string') as string) ?? '';
53
53
  const compareMode: boolean =
54
54
  selectQueryParam('compare_a') === 'true' && selectQueryParam('compare_b') === 'true';
55
+ const isColorStackLegendEnabled = selectQueryParam('color_stack_legend') === 'true';
55
56
 
56
57
  useEffect(() => {
57
58
  if (ref.current != null) {
@@ -72,7 +73,9 @@ export const IcicleGraph = memo(function IcicleGraph({
72
73
 
73
74
  return (
74
75
  <div onMouseLeave={() => dispatch(setHoveringNode(undefined))}>
75
- <ColorStackLegend navigateTo={navigateTo} compareMode={compareMode} />
76
+ {isColorStackLegendEnabled && (
77
+ <ColorStackLegend navigateTo={navigateTo} compareMode={compareMode} />
78
+ )}
76
79
  <GraphTooltip
77
80
  unit={sampleUnit}
78
81
  total={total}
@@ -64,17 +64,20 @@ const ColorStackLegend = ({
64
64
  }
65
65
 
66
66
  return (
67
- <div className="my-6 flex flex-wrap justify-center gap-2">
67
+ <div className="my-4 flex w-full flex-wrap justify-start">
68
68
  {stackColorArray.map(([feature, color]) => {
69
69
  const filteringAllowed = feature !== EVERYTHING_ELSE;
70
70
  const isHighlighted = currentSearchString === feature;
71
71
  return (
72
72
  <div
73
73
  key={feature}
74
- className={cx('flex items-center justify-between gap-1 text-ellipsis p-1 pr-2', {
75
- 'cursor-pointer': filteringAllowed,
76
- 'bg-gray-200 dark:bg-gray-800': isHighlighted,
77
- })}
74
+ className={cx(
75
+ 'flex-no-wrap mb-1 flex w-1/5 items-center justify-between text-ellipsis p-1',
76
+ {
77
+ 'cursor-pointer': filteringAllowed,
78
+ 'bg-gray-200 dark:bg-gray-800': isHighlighted,
79
+ }
80
+ )}
78
81
  onClick={() => {
79
82
  if (!filteringAllowed) {
80
83
  return;
@@ -86,19 +89,25 @@ const ColorStackLegend = ({
86
89
  setSearchString(feature);
87
90
  }}
88
91
  >
89
- <div className="flex items-center">
90
- <div className="mr-1 inline-block h-4 w-4" style={{backgroundColor: color}} />
91
- <span className="text-sm">{feature}</span>
92
+ <div className="flex w-11/12 items-center justify-start">
93
+ <div className="flex w-5 items-center">
94
+ <div className="mr-1 inline-block h-4 w-4" style={{backgroundColor: color}} />
95
+ </div>
96
+ <div className="shrink overflow-hidden text-ellipsis whitespace-nowrap text-sm hover:whitespace-normal">
97
+ {feature}
98
+ </div>
99
+ </div>
100
+ <div className="flex w-1/12 justify-end">
101
+ {isHighlighted && (
102
+ <Icon
103
+ icon="radix-icons:cross-circled"
104
+ onClick={e => {
105
+ setSearchString('');
106
+ e.stopPropagation();
107
+ }}
108
+ />
109
+ )}
92
110
  </div>
93
- {isHighlighted ? (
94
- <Icon
95
- icon="radix-icons:cross-circled"
96
- onClick={e => {
97
- setSearchString('');
98
- e.stopPropagation();
99
- }}
100
- />
101
- ) : null}
102
111
  </div>
103
112
  );
104
113
  })}
@@ -95,6 +95,7 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({
95
95
  const currentSearchString = (selectQueryParam('search_string') as string) ?? '';
96
96
  const compareMode: boolean =
97
97
  selectQueryParam('compare_a') === 'true' && selectQueryParam('compare_b') === 'true';
98
+ const isColorStackLegendEnabled = selectQueryParam('color_stack_legend') === 'true';
98
99
 
99
100
  const mappings = useMemo(() => {
100
101
  // Read the mappings from the dictionary that contains all mapping strings.
@@ -230,11 +231,13 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({
230
231
 
231
232
  return (
232
233
  <div onMouseLeave={() => dispatch(setHoveringNode(undefined))}>
233
- <ColorStackLegend
234
- mappingColors={mappingColors}
235
- navigateTo={navigateTo}
236
- compareMode={compareMode}
237
- />
234
+ {isColorStackLegendEnabled && (
235
+ <ColorStackLegend
236
+ mappingColors={mappingColors}
237
+ navigateTo={navigateTo}
238
+ compareMode={compareMode}
239
+ />
240
+ )}
238
241
  {dockedMetainfo ? (
239
242
  <DockedGraphTooltip
240
243
  table={table}
@@ -18,7 +18,13 @@ import {Icon} from '@iconify/react';
18
18
 
19
19
  import {Flamegraph, FlamegraphArrow} from '@parca/client';
20
20
  import {Button, Select, useParcaContext, useURLState} from '@parca/components';
21
- import {divide, selectQueryParam, type NavigateFunction} from '@parca/utilities';
21
+ import {USER_PREFERENCES, useUserPreference} from '@parca/hooks';
22
+ import {
23
+ capitalizeOnlyFirstLetter,
24
+ divide,
25
+ selectQueryParam,
26
+ type NavigateFunction,
27
+ } from '@parca/utilities';
22
28
 
23
29
  import DiffLegend from '../components/DiffLegend';
24
30
  import IcicleGraph from './IcicleGraph';
@@ -48,6 +54,48 @@ interface ProfileIcicleGraphProps {
48
54
  error?: any;
49
55
  }
50
56
 
57
+ const ErrorContent = ({errorMessage}: {errorMessage: string}): JSX.Element => {
58
+ return <div className="flex justify-center p-10">{errorMessage}</div>;
59
+ };
60
+
61
+ const ShowHideLegendButton = ({navigateTo}: {navigateTo?: NavigateFunction}): JSX.Element => {
62
+ const [colorStackLegend, setStoreColorStackLegend] = useURLState({
63
+ param: 'color_stack_legend',
64
+ navigateTo,
65
+ });
66
+
67
+ const compareMode: boolean =
68
+ selectQueryParam('compare_a') === 'true' && selectQueryParam('compare_b') === 'true';
69
+
70
+ const isColorStackLegendEnabled = colorStackLegend === 'true';
71
+
72
+ const [colorProfileName] = useUserPreference<string>(
73
+ USER_PREFERENCES.FLAMEGRAPH_COLOR_PROFILE.key
74
+ );
75
+
76
+ const setColorStackLegend = useCallback(
77
+ (value: string): void => {
78
+ setStoreColorStackLegend(value);
79
+ },
80
+ [setStoreColorStackLegend]
81
+ );
82
+
83
+ return (
84
+ <>
85
+ {colorProfileName === 'default' || compareMode ? null : (
86
+ <Button
87
+ className="gap-2"
88
+ variant="neutral"
89
+ onClick={() => setColorStackLegend(isColorStackLegendEnabled ? 'false' : 'true')}
90
+ >
91
+ {isColorStackLegendEnabled ? 'Hide legend' : 'Show legend'}
92
+ <Icon icon={isColorStackLegendEnabled ? 'ph:eye-closed' : 'ph:eye'} width={20} />
93
+ </Button>
94
+ )}
95
+ </>
96
+ );
97
+ };
98
+
51
99
  const GroupAndSortActionButtons = ({navigateTo}: {navigateTo?: NavigateFunction}): JSX.Element => {
52
100
  const [storeSortBy = FIELD_FUNCTION_NAME, setStoreSortBy] = useURLState({
53
101
  param: 'sort_by',
@@ -113,7 +161,7 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
113
161
  error,
114
162
  width,
115
163
  }: ProfileIcicleGraphProps): JSX.Element {
116
- const {loader} = useParcaContext();
164
+ const {loader, onError, authenticationErrorMessage} = useParcaContext();
117
165
  const compareMode: boolean =
118
166
  selectQueryParam('compare_a') === 'true' && selectQueryParam('compare_b') === 'true';
119
167
 
@@ -160,16 +208,14 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
160
208
  <div className="flex w-full justify-end gap-2 pb-2">
161
209
  <div className="ml-2 flex w-full items-end justify-between gap-2">
162
210
  {arrow !== undefined && <GroupAndSortActionButtons navigateTo={navigateTo} />}
163
- <div>
164
- <Button
165
- color="neutral"
166
- onClick={() => setNewCurPath([])}
167
- disabled={curPath.length === 0}
168
- variant="neutral"
169
- >
170
- Reset View
171
- </Button>
172
- </div>
211
+ <ShowHideLegendButton navigateTo={navigateTo} />
212
+ <Button
213
+ variant="neutral"
214
+ onClick={() => setNewCurPath([])}
215
+ disabled={curPath.length === 0}
216
+ >
217
+ Reset View
218
+ </Button>
173
219
  </div>
174
220
  </div>
175
221
  );
@@ -180,8 +226,13 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
180
226
  }
181
227
 
182
228
  if (error != null) {
183
- console.error('Error: ', error);
184
- return <div className="flex justify-center p-10">An error occurred: {error.message}</div>;
229
+ onError?.(error);
230
+
231
+ if (authenticationErrorMessage !== undefined && error.code === 'UNAUTHENTICATED') {
232
+ return <ErrorContent errorMessage={authenticationErrorMessage} />;
233
+ }
234
+
235
+ return <ErrorContent errorMessage={capitalizeOnlyFirstLetter(error.message)} />;
185
236
  }
186
237
 
187
238
  if (graph === undefined && arrow === undefined)
@@ -18,7 +18,7 @@ import {RpcError} from '@protobuf-ts/runtime-rpc';
18
18
  import {Duration, Label, QueryRangeResponse, QueryServiceClient, Timestamp} from '@parca/client';
19
19
  import {DateTimeRange, useGrpcMetadata, useParcaContext} from '@parca/components';
20
20
  import {Query} from '@parca/parser';
21
- import {getStepDuration} from '@parca/utilities';
21
+ import {capitalizeOnlyFirstLetter, getStepDuration} from '@parca/utilities';
22
22
 
23
23
  import {MergedProfileSelection, ProfileSelection} from '..';
24
24
  import MetricsGraph from '../MetricsGraph';
@@ -29,6 +29,17 @@ interface ProfileMetricsEmptyStateProps {
29
29
  message: string;
30
30
  }
31
31
 
32
+ const ErrorContent = ({errorMessage}: {errorMessage: string}): JSX.Element => {
33
+ return (
34
+ <div
35
+ className="relative rounded border border-red-400 bg-red-100 px-4 py-3 text-red-700"
36
+ role="alert"
37
+ >
38
+ <span className="block sm:inline">{errorMessage}</span>
39
+ </div>
40
+ );
41
+ };
42
+
32
43
  export const ProfileMetricsEmptyState = ({message}: ProfileMetricsEmptyStateProps): JSX.Element => {
33
44
  return (
34
45
  <div className="flex h-full w-full flex-col items-center justify-center">
@@ -114,12 +125,12 @@ const ProfileMetricsGraph = ({
114
125
  }: ProfileMetricsGraphProps): JSX.Element => {
115
126
  const {isLoading, response, error} = useQueryRange(queryClient, queryExpression, from, to);
116
127
  const isLoaderVisible = useDelayedLoader(isLoading);
117
- const {loader, onError, perf} = useParcaContext();
128
+ const {loader, onError, perf, authenticationErrorMessage} = useParcaContext();
118
129
  const {width, height, margin} = useMetricsGraphDimensions(comparing);
119
130
 
120
131
  useEffect(() => {
121
132
  if (error !== null) {
122
- onError?.(error, 'metricsGraph');
133
+ onError?.(error);
123
134
  }
124
135
  }, [error, onError]);
125
136
 
@@ -139,15 +150,11 @@ const ProfileMetricsGraph = ({
139
150
  }
140
151
 
141
152
  if (error !== null) {
142
- return (
143
- <div
144
- className="relative rounded border border-red-400 bg-red-100 px-4 py-3 text-red-700"
145
- role="alert"
146
- >
147
- <strong className="font-bold">Error! </strong>
148
- <span className="block sm:inline">{error.message}</span>
149
- </div>
150
- );
153
+ if (authenticationErrorMessage !== undefined && error.code === 'UNAUTHENTICATED') {
154
+ return <ErrorContent errorMessage={authenticationErrorMessage} />;
155
+ }
156
+
157
+ return <ErrorContent errorMessage={capitalizeOnlyFirstLetter(error.message)} />;
151
158
  }
152
159
 
153
160
  if (dataAvailable) {