Skip to content

Commit

Permalink
ref(laravel-insights): Split into multiple files
Browse files Browse the repository at this point in the history
  • Loading branch information
ArthurKnaus committed Mar 5, 2025
1 parent d4f1917 commit 152b323
Show file tree
Hide file tree
Showing 11 changed files with 1,253 additions and 1,191 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {ToolRibbon} from 'sentry/views/insights/common/components/ribbon';
import {useOnboardingProject} from 'sentry/views/insights/common/queries/useOnboardingProject';
import {ViewTrendsButton} from 'sentry/views/insights/common/viewTrendsButton';
import {BackendHeader} from 'sentry/views/insights/pages/backend/backendPageHeader';
import {LaravelOverviewPage} from 'sentry/views/insights/pages/backend/laravelOverviewPage';
import {LaravelOverviewPage} from 'sentry/views/insights/pages/backend/laravel';
import {
BACKEND_LANDING_TITLE,
OVERVIEW_PAGE_ALLOWED_OPS,
Expand Down
157 changes: 157 additions & 0 deletions static/app/views/insights/pages/backend/laravel/cachesWidget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import {Fragment, useMemo} from 'react';
import {Link} from 'react-router-dom';
import styled from '@emotion/styled';

import {space} from 'sentry/styles/space';
import {useApiQuery} from 'sentry/utils/queryClient';
import {MutableSearch} from 'sentry/utils/tokenizeSearch';
import useOrganization from 'sentry/utils/useOrganization';
import {MISSING_DATA_MESSAGE} from 'sentry/views/dashboards/widgets/common/settings';
import {TimeSeriesWidgetVisualization} from 'sentry/views/dashboards/widgets/timeSeriesWidget/timeSeriesWidgetVisualization';
import {Widget} from 'sentry/views/dashboards/widgets/widget/widget';
import type {DiscoverSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries';
import {useSpanMetricsTopNSeries} from 'sentry/views/insights/common/queries/useSpanMetricsTopNSeries';
import {convertSeriesToTimeseries} from 'sentry/views/insights/common/utils/convertSeriesToTimeseries';
import {usePageFilterChartParams} from 'sentry/views/insights/pages/backend/laravel/utils';

export function CachesWidget({query}: {query?: string}) {
const organization = useOrganization();
const pageFilterChartParams = usePageFilterChartParams();

const cachesRequest = useApiQuery<{
data: Array<{
'cache_miss_rate()': number;
'project.id': string;
transaction: string;
}>;
}>(
[
`/organizations/${organization.slug}/events/`,
{
query: {
...pageFilterChartParams,
dataset: 'spansMetrics',
field: ['transaction', 'project.id', 'cache_miss_rate()'],
query: `span.op:[cache.get_item,cache.get] ${query}`,
sort: '-cache_miss_rate()',
per_page: 4,
},
},
],
{staleTime: 0}
);

const timeSeriesRequest = useSpanMetricsTopNSeries({
search: new MutableSearch(
// Cannot use transaction:[value1, value2] syntax as
// MutableSearch might escape it to transactions:"[value1, value2]" for some values
cachesRequest.data?.data
.map(item => `transaction:"${item.transaction}"`)
.join(' OR ') || ''
),
fields: ['transaction', 'cache_miss_rate()'],
yAxis: ['cache_miss_rate()'],
sorts: [
{
field: 'cache_miss_rate()',
kind: 'desc',
},
],
topEvents: 4,
enabled: !!cachesRequest.data?.data,
});

const timeSeries = useMemo<DiscoverSeries[]>(() => {
if (!timeSeriesRequest.data && timeSeriesRequest.meta) {
return [];
}

return Object.keys(timeSeriesRequest.data).map(key => {
const seriesData = timeSeriesRequest.data[key]!;
return {
...seriesData,
// TODO(aknaus): useSpanMetricsTopNSeries does not return the meta for the series
meta: {
fields: {
[seriesData.seriesName]: 'percentage',
},
units: {
[seriesData.seriesName]: '%',
},
},
};
});
}, [timeSeriesRequest.data, timeSeriesRequest.meta]);

const isLoading = timeSeriesRequest.isLoading || cachesRequest.isLoading;
const error = timeSeriesRequest.error || cachesRequest.error;

const hasData =
cachesRequest.data && cachesRequest.data.data.length > 0 && timeSeries.length > 0;

return (
<Widget
Title={<Widget.WidgetTitle title="Caches" />}
Visualization={
isLoading ? (
<TimeSeriesWidgetVisualization.LoadingPlaceholder />
) : error ? (
<Widget.WidgetError error={error} />
) : !hasData ? (
<Widget.WidgetError error={MISSING_DATA_MESSAGE} />
) : (
<TimeSeriesWidgetVisualization
visualizationType="line"
timeSeries={timeSeries.map(convertSeriesToTimeseries)}
/>
)
}
Footer={
hasData && (
<WidgetFooterTable>
{cachesRequest.data?.data.map(item => (
<Fragment key={item.transaction}>
<OverflowCell>
<Link
to={`/insights/backend/caches?project=${item['project.id']}&transaction=${item.transaction}`}
>
{item.transaction}
</Link>
</OverflowCell>
<span>{(item['cache_miss_rate()'] * 100).toFixed(2)}%</span>
</Fragment>
))}
</WidgetFooterTable>
)
}
/>
);
}

const OverflowCell = styled('div')`
${p => p.theme.overflowEllipsis};
min-width: 0px;
`;

const WidgetFooterTable = styled('div')`
display: grid;
grid-template-columns: 1fr max-content;
margin: -${space(1)} -${space(2)};
font-size: ${p => p.theme.fontSizeSmall};
& > * {
padding: ${space(1)} ${space(1)};
}
& > *:nth-child(2n + 1) {
padding-left: ${space(2)};
}
& > *:nth-child(2n) {
padding-right: ${space(2)};
}
& > *:not(:nth-last-child(-n + 2)) {
border-bottom: 1px solid ${p => p.theme.border};
}
`;
69 changes: 69 additions & 0 deletions static/app/views/insights/pages/backend/laravel/durationWidget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import {useCallback, useMemo} from 'react';

import {CHART_PALETTE} from 'sentry/constants/chartPalette';
import type {MultiSeriesEventsStats} from 'sentry/types/organization';
import type {EventsMetaType} from 'sentry/utils/discover/eventView';
import {useApiQuery} from 'sentry/utils/queryClient';
import useOrganization from 'sentry/utils/useOrganization';
import {InsightsLineChartWidget} from 'sentry/views/insights/common/components/insightsLineChartWidget';
import type {DiscoverSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries';
import {usePageFilterChartParams} from 'sentry/views/insights/pages/backend/laravel/utils';

export function DurationWidget({query}: {query?: string}) {
const organization = useOrganization();
const pageFilterChartParams = usePageFilterChartParams();

const {data, isLoading, error} = useApiQuery<MultiSeriesEventsStats>(
[
`/organizations/${organization.slug}/events-stats/`,
{
query: {
...pageFilterChartParams,
dataset: 'spans',
yAxis: ['avg(span.duration)', 'p95(span.duration)'],
orderby: 'avg(span.duration)',
partial: 1,
useRpc: 1,
query: `span.op:http.server ${query}`.trim(),
},
},
],
{staleTime: 0}
);

const getTimeSeries = useCallback(
(field: string, color?: string): DiscoverSeries | undefined => {
const series = data?.[field];
if (!series) {
return undefined;
}

return {
data: series.data.map(([time, [value]]) => ({
value: value?.count!,
name: new Date(time * 1000).toISOString(),
})),
seriesName: field,
meta: series.meta as EventsMetaType,
color,
} satisfies DiscoverSeries;
},
[data]
);

const timeSeries = useMemo(() => {
return [
getTimeSeries('avg(span.duration)', CHART_PALETTE[1][0]),
getTimeSeries('p95(span.duration)', CHART_PALETTE[1][1]),
].filter(series => !!series);
}, [getTimeSeries]);

return (
<InsightsLineChartWidget
title="Duration"
isLoading={isLoading}
error={error}
series={timeSeries}
/>
);
}
Loading

0 comments on commit 152b323

Please sign in to comment.