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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9,380 changes: 6,308 additions & 3,072 deletions web/package-lock.json

Large diffs are not rendered by default.

25 changes: 17 additions & 8 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,21 +75,30 @@
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.0",
"@hookform/resolvers": "^3.3.4",
"@mui/material": "^5.15.14",
"@patternfly/react-charts": "6.92.0",
"@patternfly/react-table": "^5.2.4",
"@perses-dev/components": "0.49.0-rc.1",
"@perses-dev/dashboards": "0.49.0-rc.1",
"@perses-dev/panels-plugin": "0.49.0-rc.1",
"@perses-dev/plugin-system": "0.49.0-rc.1",
"@perses-dev/prometheus-plugin": "0.49.0-rc.1",
"@perses-dev/tempo-plugin": "0.49.0-rc.1",
"@tanstack/react-query": "^4.7.1",
"@perses-dev/core": "0.51.0-rc.0",
"@perses-dev/dashboards": "0.51.0-rc.0",
"@perses-dev/plugin-system": "0.51.0-rc.0",
"@perses-dev/scatter-chart-plugin": "^0.6.0",
"@perses-dev/tempo-plugin": "^0.51.0-beta.2",
"@perses-dev/trace-table-plugin": "^0.6.0",
"@perses-dev/tracing-gantt-chart-plugin": "^0.6.0",
"@tanstack/react-query": "4.36.1",
"copy-webpack-plugin": "^12.0.2",
"i18next": "^23.10.0",
"i18next-http-backend": "^2.5.0",
"i18next-parser": "^8.13.0",
"pluralize": "^8.0.0",
"use-query-params": "^2.2.1"
},
"overrides": {
"@mui/x-data-grid": "7.20.0",
"@perses-dev/components": "0.51.0-rc.0",
"@perses-dev/core": "0.51.0-rc.0",
"@perses-dev/dashboards": "0.51.0-rc.0",
"@perses-dev/plugin-system": "0.51.0-rc.0",
"@tanstack/react-query": "4.36.1",
"react-router-dom": "5.2.0"
}
}
2 changes: 1 addition & 1 deletion web/src/components/DurationDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
Stack,
} from '@patternfly/react-core';
import { useTranslation } from 'react-i18next';
import { DurationString } from '@perses-dev/prometheus-plugin';
import { DurationString } from '@perses-dev/core';

type TimeRangeSelectOption = {
display: string;
Expand Down
65 changes: 41 additions & 24 deletions web/src/components/PersesWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ import { ThemeProvider } from '@mui/material';
import {
DataQueriesProvider,
dynamicImportPluginLoader,
PluginModuleResource,
PanelPlugin,
PanelProps,
PluginRegistry,
TimeRangeProvider,
useDataQueries,
useDataQueriesContext,
} from '@perses-dev/plugin-system';
import {
DatasourceResource,
Expand All @@ -23,10 +24,12 @@ import {
TimeRangeValue,
UnknownSpec,
} from '@perses-dev/core';
import panelsResource from '@perses-dev/panels-plugin/plugin.json';
import tempoResource from '@perses-dev/tempo-plugin/plugin.json';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { DatasourceApi, DatasourceStoreProvider, VariableProvider } from '@perses-dev/dashboards';
import * as tempoPlugin from '@perses-dev/tempo-plugin';
import * as scatterChartPlugin from '@perses-dev/scatter-chart-plugin';
import * as traceTablePlugin from '@perses-dev/trace-table-plugin';
import * as tracingGanttChartPlugin from '@perses-dev/tracing-gantt-chart-plugin';
import { ChartThemeColor, getThemeColors } from '@patternfly/react-charts';
import { TempoInstance } from '../hooks/useTempoInstance';
import { getProxyURLFor } from '../hooks/api';
Expand Down Expand Up @@ -75,16 +78,12 @@ const patternflyChartsMultiUnorderedPalette = getThemeColors(
// PluginRegistry configuration to allow access to
// visualization panels/charts (@perses-dev/panels-plugin)
// and data handlers for tempo (@perses-dev/tempo-plugin).
const pluginLoader = dynamicImportPluginLoader([
{
resource: panelsResource as PluginModuleResource,
importPlugin: () => import('@perses-dev/panels-plugin'),
},
{
resource: tempoResource as PluginModuleResource,
importPlugin: () => import('@perses-dev/tempo-plugin'),
},
]);
const pluginLoader = dynamicImportPluginLoader(
[tempoPlugin, scatterChartPlugin, traceTablePlugin, tracingGanttChartPlugin].map((x) => ({
resource: x.getPluginModule(),
importPlugin: () => Promise.resolve(x),
})),
);

const queryClient = new QueryClient({
defaultOptions: {
Expand All @@ -99,6 +98,9 @@ interface PersesWrapperProps {
children?: React.ReactNode;
}

/**
* PersesWrapper initializes the MaterialUI theme, Perses plugin registry, and query client.
*/
export function PersesWrapper({ children }: PersesWrapperProps) {
const { theme } = usePatternFlyTheme();

Expand Down Expand Up @@ -139,6 +141,9 @@ interface PersesDashboardWrapperProps {
children?: React.ReactNode;
}

/**
* PersesDashboardWrapper initializes the dashboard time range and variable providers.
*/
export function PersesDashboardWrapper({
timeRange = { pastDuration: '0s' },
setTimeRange,
Expand All @@ -158,6 +163,9 @@ interface PersesTempoDatasourceWrapperProps {
children?: React.ReactNode;
}

/**
* PersesTempoDatasourceWrapper initializes the Tempo data source.
*/
export function PersesTempoDatasourceWrapper({
tempo,
queries,
Expand Down Expand Up @@ -191,17 +199,21 @@ export function PersesTempoDatasourceWrapper({
);
}

interface TracePanelWrapperProps {
interface PersesPanelPluginWrapperProps<T> {
plugin: T;
noResults?: React.ReactNode;
children?: React.ReactNode;
}

/**
* TraceQueryPanelWrapper intercepts the trace query status and displays PatternFly native empty and loading states
* instead of the Material UI empty and loading states used by Perses.
* PersesPanelWrapper renders a Perses panel plugin and shows PatternFly empty, loading states and error states.
*/
export function TraceQueryPanelWrapper({ noResults, children }: TracePanelWrapperProps) {
const { isFetching, isLoading, queryResults } = useDataQueries('TraceQuery');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function PersesPanelPluginWrapper<T extends PanelPlugin<Spec, PanelProps<Spec>>, Spec = any>(
props: PersesPanelPluginWrapperProps<T> &
Omit<React.ComponentProps<T['PanelComponent']>, 'queryResults'>,
) {
const { plugin, noResults, ...otherProps } = props;
const { isFetching, isLoading, queryResults } = useDataQueriesContext();

if (isLoading || isFetching) {
return <LoadingState />;
Expand All @@ -212,16 +224,21 @@ export function TraceQueryPanelWrapper({ noResults, children }: TracePanelWrappe
return <ErrorAlert error={queryError.error as Error} />;
}

const dataFound = queryResults.some(
(traceData) => (traceData.data?.searchResult ?? []).length > 0 || traceData.data?.trace,
const queryResultsWithData = queryResults.flatMap((q) =>
q.data ? [{ data: q.data, definition: q.definition }] : [],
);
const traceDataFound = queryResultsWithData.some(
({ data }) =>
('searchResult' in data && data.searchResult && data.searchResult.length > 0) ||
('trace' in data && data.trace),
);
if (!dataFound && noResults) {
if (!traceDataFound && noResults) {
return <>{noResults}</>;
}

return (
<ErrorBoundary FallbackComponent={ErrorAlert} resetKeys={[]}>
{children}
<plugin.PanelComponent queryResults={queryResultsWithData} {...otherProps} />
</ErrorBoundary>
);
}
75 changes: 46 additions & 29 deletions web/src/pages/TraceDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ import {
import { Helmet, HelmetProvider } from 'react-helmet-async';
import { useTranslation } from 'react-i18next';
import { Link, useLocation, useParams } from 'react-router-dom';
import { TracingGanttChart } from '@perses-dev/panels-plugin';
import {
PersesDashboardWrapper,
PersesTempoDatasourceWrapper,
PersesWrapper,
TraceQueryPanelWrapper,
PersesPanelPluginWrapper,
} from '../components/PersesWrapper';
import { TraceAttributeValue } from '@perses-dev/core';
import { otlpcommonv1 } from '@perses-dev/core';
import { useDataQueries } from '@perses-dev/plugin-system';
import { useTempoInstance } from '../hooks/useTempoInstance';
import { TracingGanttChart } from '@perses-dev/tracing-gantt-chart-plugin';

interface RouteParams {
traceId: string;
Expand All @@ -35,13 +35,6 @@ export function TraceDetailPage() {

return (
<>
<HelmetProvider>
<Helmet>
<title>
{t('Trace')} {traceId} · {t('Tracing')}
</title>
</Helmet>
</HelmetProvider>
<Page>
<PageSection variant="light">
<Stack>
Expand All @@ -58,17 +51,14 @@ export function TraceDetailPage() {
tempo={tempo}
queries={[{ kind: 'TempoTraceQuery', spec: { query: traceId } }]}
>
<Title headingLevel="h1">
<TraceTitle />
</Title>
<PageTitle />
<Divider className="pf-v5-u-my-md" />
<StackItem isFilled>
<TraceQueryPanelWrapper>
<TracingGanttChart.PanelComponent
spec={{ visual: { palette: { mode: 'categorical' } } }}
attributeLinks={attributeLinks}
/>
</TraceQueryPanelWrapper>
<PersesPanelPluginWrapper
plugin={TracingGanttChart}
spec={{ visual: { palette: { mode: 'categorical' } } }}
attributeLinks={attributeLinks}
/>
</StackItem>
</PersesTempoDatasourceWrapper>
</PersesDashboardWrapper>
Expand All @@ -80,25 +70,52 @@ export function TraceDetailPage() {
);
}

function TraceTitle() {
const { traceId } = useParams<RouteParams>();
const { queryResults } = useDataQueries('TraceQuery');
const trace = queryResults[0]?.data?.trace;
function PageTitle() {
const { t } = useTranslation('plugin__distributed-tracing-console-plugin');
const traceName = useTraceName();

if (!trace) {
return <>{traceId}</>;
}
return (
<>
{trace.rootSpan.resource.serviceName}: {trace.rootSpan.name}
<HelmetProvider>
<Helmet>
<title>
{t('Trace')} {traceName} · {t('Tracing')}
</title>
</Helmet>
</HelmetProvider>
<Title headingLevel="h1">{traceName}</Title>
</>
);
}

const sval = (val?: TraceAttributeValue) =>
function useTraceName(): string {
const { traceId } = useParams<RouteParams>();
const { queryResults } = useDataQueries('TraceQuery');
const trace = queryResults[0]?.data?.trace;

for (const resourceSpan of trace?.resourceSpans ?? []) {
for (const scopeSpan of resourceSpan.scopeSpans) {
for (const span of scopeSpan.spans) {
if (!span.parentSpanId) {
// found a root span, look for service name attribute
for (const attr of resourceSpan.resource?.attributes ?? []) {
if (attr.key === 'service.name' && 'stringValue' in attr.value) {
return `${attr.value.stringValue}: ${span.name}`;
}
}
}
}
}
}

// return traceId if span is not loaded or root span is not found
return traceId ?? '';
}

const sval = (val?: otlpcommonv1.AnyValue) =>
val && 'stringValue' in val ? encodeURIComponent(val.stringValue) : '';

const attributeLinks: Record<string, (attrs: Record<string, TraceAttributeValue>) => string> = {
const attributeLinks: Record<string, (attrs: Record<string, otlpcommonv1.AnyValue>) => string> = {
'k8s.namespace.name': (attrs) => `/k8s/cluster/namespaces/${sval(attrs['k8s.namespace.name'])}`,
'k8s.node.name': (attrs) => `/k8s/cluster/nodes/${sval(attrs['k8s.node.name'])}`,
'k8s.deployment.name': (attrs) =>
Expand Down
2 changes: 1 addition & 1 deletion web/src/pages/TracesPage/QueryEditor/TraceQLEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import CodeMirror, { EditorView, keymap } from '@uiw/react-codemirror';
import { TraceQLExtension } from '@perses-dev/tempo-plugin';
import { TraceQLExtension } from '@perses-dev/tempo-plugin/lib/components/TraceQLExtension';
import { TempoInstance } from '../../../hooks/useTempoInstance';
import { getProxyURLFor } from '../../../hooks/api';
import { usePatternFlyTheme } from '../../../components/console/utils/usePatternFlyTheme';
Expand Down
35 changes: 16 additions & 19 deletions web/src/pages/TracesPage/ScatterPlot.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useState } from 'react';
import React, { useState } from 'react';
import {
Bullseye,
Button,
Expand All @@ -8,25 +8,22 @@ import {
FlexItem,
} from '@patternfly/react-core';
import { useTranslation } from 'react-i18next';
import { TraceQueryPanelWrapper } from '../../components/PersesWrapper';
import { ScatterChart } from '@perses-dev/panels-plugin';
import { PersesPanelPluginWrapper } from '../../components/PersesWrapper';
import { ExpandIcon, CompressIcon } from '@patternfly/react-icons';
import { useRefWidth } from '../../components/console/utils/ref-width-hook';
import { useHistory } from 'react-router-dom';
import { linkToTraceDetailPage } from '../../links';
import { ScatterChart } from '@perses-dev/scatter-chart-plugin';

export function ScatterPlot() {
const { t } = useTranslation('plugin__distributed-tracing-console-plugin');
const history = useHistory();
const [isVisible, setVisible] = useState(true);
const [ref, width] = useRefWidth();

const clickHandler = useCallback(
(data: { traceId: string }) => {
history.push(linkToTraceDetailPage(data.traceId));
},
[history],
);
const clickHandler = (data: { traceId: string }) => {
history.push(linkToTraceDetailPage(data.traceId));
};

const noResults = (
<Bullseye>
Expand Down Expand Up @@ -67,16 +64,16 @@ export function ScatterPlot() {
border: 'var(--pf-global--BorderWidth--sm) solid var(--pf-global--BorderColor--100)',
}}
>
<TraceQueryPanelWrapper noResults={noResults}>
<ScatterChart.PanelComponent
contentDimensions={{
width,
height: 200,
}}
spec={{}}
onClick={clickHandler}
/>
</TraceQueryPanelWrapper>
<PersesPanelPluginWrapper
plugin={ScatterChart}
noResults={noResults}
contentDimensions={{
width,
height: 200,
}}
spec={{}}
onClick={clickHandler}
/>
</div>
)}
</div>
Expand Down
Loading