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
10,421 changes: 6,608 additions & 3,813 deletions web/package-lock.json

Large diffs are not rendered by default.

24 changes: 17 additions & 7 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,23 +79,33 @@
"@emotion/styled": "^11.11.0",
"@grafana/lezer-traceql": "^0.0.22",
"@hookform/resolvers": "^3.3.4",
"@mui/material": "^5.15.14",
"@patternfly/react-charts": "6.92.0",
"@patternfly/react-core": "^6.2.0",
"@patternfly/react-icons": "^5.4.2",
"@patternfly/react-templates": "^6.2.2",
"@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/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",
"lodash": "^4.17.21",
"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.3.4"
}
}
61 changes: 37 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,9 +24,11 @@ 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 { 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 @@ -74,21 +77,20 @@ 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),
})),
);

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 @@ -127,6 +129,9 @@ interface PersesDashboardWrapperProps {
children?: React.ReactNode;
}

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

/**
* PersesTempoDatasourceWrapper initializes the Tempo data source.
*/
export function PersesTempoDatasourceWrapper({
tempo,
queries,
Expand Down Expand Up @@ -179,17 +187,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 @@ -200,16 +212,17 @@ 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 }] : [],
);
if (!dataFound && noResults) {

if (queryResultsWithData.length === 0 && noResults) {
return <>{noResults}</>;
}

return (
<ErrorBoundary FallbackComponent={ErrorAlert} resetKeys={[]}>
{children}
<plugin.PanelComponent queryResults={queryResultsWithData} {...otherProps} />
</ErrorBoundary>
);
}
42 changes: 27 additions & 15 deletions web/src/pages/TraceDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ import { Breadcrumb, BreadcrumbItem, Divider, PageSection, Title } from '@patter
import { Helmet } from 'react-helmet';
import { useTranslation } from 'react-i18next';
import { Link, useLocation, useParams } from 'react-router-dom-v5-compat';
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 { TracingApp } from '../TracingApp';
import { memo } from 'react';
import { TracingGanttChart } from '@perses-dev/tracing-gantt-chart-plugin';

function TraceDetailPage() {
return (
Expand Down Expand Up @@ -52,12 +52,11 @@ function TraceDetailPageBody() {
<Divider className="pf-v6-u-mt-md" />
</PageSection>
<PageSection isFilled hasBodyWrapper={false}>
<TraceQueryPanelWrapper>
<TracingGanttChart.PanelComponent
spec={{ visual: { palette: { mode: 'categorical' } } }}
attributeLinks={attributeLinks}
/>
</TraceQueryPanelWrapper>
<PersesPanelPluginWrapper
plugin={TracingGanttChart}
spec={{ visual: { palette: { mode: 'categorical' } } }}
attributeLinks={attributeLinks}
/>
</PageSection>
</PersesTempoDatasourceWrapper>
);
Expand All @@ -79,21 +78,34 @@ function PageTitle() {
);
}

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

if (!trace) {
return traceId;
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 `${trace.rootSpan.resource.serviceName}: ${trace.rootSpan.name}`;

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

const sval = (val?: TraceAttributeValue) =>
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
24 changes: 12 additions & 12 deletions web/src/pages/TracesPage/ScatterPlot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ 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 { useNavigate } from 'react-router-dom-v5-compat';
import { linkToTraceDetailPage } from '../../links';
import { ScatterChart } from '@perses-dev/scatter-chart-plugin';

export function ScatterPlot() {
const { t } = useTranslation('plugin__distributed-tracing-console-plugin');
Expand Down Expand Up @@ -62,16 +62,16 @@ export function ScatterPlot() {
borderRadius: 'var(--pf-t--global--border--radius--small)',
}}
>
<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
2 changes: 1 addition & 1 deletion web/src/pages/TracesPage/Toolbar/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
20 changes: 10 additions & 10 deletions web/src/pages/TracesPage/TraceTable.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { TraceQueryPanelWrapper } from '../../components/PersesWrapper';
import { TraceTable as PersesTraceTable } from '@perses-dev/panels-plugin';
import { PersesPanelPluginWrapper } from '../../components/PersesWrapper';
import { TraceTable as PersesTraceTable } from '@perses-dev/trace-table-plugin';
import {
Button,
EmptyState,
Expand Down Expand Up @@ -36,14 +36,14 @@ export function TraceTable({ setQuery }: TraceTableProps) {
);

return (
<TraceQueryPanelWrapper noResults={noResults}>
<div className="dt-plugin-trace-table">
<PersesTraceTable.PanelComponent
spec={{ visual: { palette: { mode: 'categorical' } } }}
traceLink={traceDetailLink}
/>
</div>
</TraceQueryPanelWrapper>
<div className="dt-plugin-trace-table">
<PersesPanelPluginWrapper
plugin={PersesTraceTable}
noResults={noResults}
spec={{ visual: { palette: { mode: 'categorical' } } }}
traceLink={traceDetailLink}
/>
</div>
);
}

Expand Down