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
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {Fragment, useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {useTheme} from '@emotion/react';
import styled from '@emotion/styled';
import {useQuery} from '@tanstack/react-query';

import {Button} from '@sentry/scraps/button';
import {Grid} from '@sentry/scraps/layout';
Expand All @@ -20,7 +21,7 @@ import {
OrderBy,
sortedFlags,
} from 'sentry/components/events/featureFlags/utils';
import {useOrganizationFlagLog} from 'sentry/components/featureFlags/hooks/useOrganizationFlagLog';
import {organizationFlagLogOptions} from 'sentry/components/featureFlags/hooks/useOrganizationFlagLog';
import {FeedbackButton} from 'sentry/components/feedbackButton/feedbackButton';
import {useDrawer} from 'sentry/components/globalDrawer';
import {useLegacyEventSuspectFlags} from 'sentry/components/issues/suspect/useLegacyEventSuspectFlags';
Expand Down Expand Up @@ -77,14 +78,16 @@ function BaseEventFeatureFlagList({event, group, project}: EventFeatureFlagSecti
const viewAllButtonRef = useRef<HTMLButtonElement>(null);

const eventView = useIssueDetailsEventView({group});
const {data: rawFlagData} = useOrganizationFlagLog({
organization,
query: {
start: eventView.start,
end: eventView.end,
statsPeriod: eventView.statsPeriod,
},
});
const {data: rawFlagData} = useQuery(
organizationFlagLogOptions({
organization,
query: {
start: eventView.start,
end: eventView.end,
statsPeriod: eventView.statsPeriod,
},
})
);
const location = useLocation();

// issue list params we want to preserve in the search
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@ import {GridEditable, type GridColumnOrder} from 'sentry/components/tables/gridE
import {t} from 'sentry/locale';
import {trackAnalytics} from 'sentry/utils/analytics';
import {FIELD_FORMATTERS} from 'sentry/utils/discover/fieldRenderers';
import type {RequestError} from 'sentry/utils/requestError/requestError';
import {useNavigate} from 'sentry/utils/useNavigate';
import {useOrganization} from 'sentry/utils/useOrganization';

export type ColumnKey = 'provider' | 'flag' | 'action' | 'createdAt';

interface FeatureFlagsLogTableProps {
columns: Array<GridColumnOrder<ColumnKey>>;
error: RequestError | null;
error: Error | null;
flags: RawFlag[];
isPending: boolean;
pageLinks: string | null;
Expand Down
63 changes: 32 additions & 31 deletions static/app/components/featureFlags/hooks/useFlagsInEvent.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import {skipToken, useQuery} from '@tanstack/react-query';

import {useFetchGroupAndEvent} from 'sentry/components/featureFlags/hooks/useFetchGroupAndEvent';
import {
useOrganizationFlagLog,
organizationFlagLogOptions,
useOrganizationFlagLogInfinite,
} from 'sentry/components/featureFlags/hooks/useOrganizationFlagLog';
import type {Event} from 'sentry/types/event';
import type {Group} from 'sentry/types/group';
import {defined} from 'sentry/utils';
import {getApiUrl} from 'sentry/utils/api/getApiUrl';
import {useApiQuery} from 'sentry/utils/queryClient';
import {apiOptions, selectJsonWithHeaders} from 'sentry/utils/api/apiOptions';
import {useOrganization} from 'sentry/utils/useOrganization';
import {useGroup} from 'sentry/views/issueDetails/useGroup';

Expand Down Expand Up @@ -48,23 +49,25 @@ export function useFlagsInEventPaginated({
});

const {
data: rawFlagData,
getResponseHeader,
data: rawFlagResp,
isPending: isFlagsPending,
isError: isFlagsError,
error: flagsError,
} = useOrganizationFlagLog({
organization,
query: {
...query,
flag: eventFlags,
},
} = useQuery({
...organizationFlagLogOptions({
organization,
query: {
...query,
flag: eventFlags,
},
}),
enabled: enabled && Boolean(eventFlags?.length),
select: selectJsonWithHeaders,
});
const pageLinks = getResponseHeader?.('Link') ?? null;
const pageLinks = rawFlagResp?.headers.Link ?? null;

return {
flags: rawFlagData?.data ?? [],
flags: rawFlagResp?.json?.data ?? [],
event,
group,
pageLinks,
Expand Down Expand Up @@ -108,29 +111,27 @@ export function useFlagsInEvent({
const group = groupProp ?? groupData;

const projectSlug = group?.project.slug;
const eventEnabled =
enabled && Boolean(eventId && projectSlug && organization.slug) && !eventProp;
const {
data: eventData,
isPending: isEventPending,
isError: isEventError,
error: eventError,
} = useApiQuery<Event>(
[
getApiUrl(
'/organizations/$organizationIdOrSlug/events/$projectIdOrSlug:$eventId/',
{
path: {
organizationIdOrSlug: organization.slug,
projectIdOrSlug: projectSlug!,
eventId: eventId!,
},
}
),
],
{
staleTime: Infinity,
enabled:
enabled && Boolean(eventId && projectSlug && organization.slug) && !eventProp,
}
} = useQuery(
apiOptions.as<Event>()(
'/organizations/$organizationIdOrSlug/events/$projectIdOrSlug:$eventId/',
{
path: eventEnabled
? {
organizationIdOrSlug: organization.slug,
projectIdOrSlug: projectSlug!,
eventId: eventId!,
}
: skipToken,
staleTime: Infinity,
}
)
);
const event = eventProp ?? eventData;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,25 @@
import {useEffect} from 'react';
import {skipToken, useInfiniteQuery} from '@tanstack/react-query';

import type {RawFlagData} from 'sentry/components/featureFlags/utils';
import type {Organization} from 'sentry/types/organization';
import {getApiUrl} from 'sentry/utils/api/getApiUrl';
import {useApiQuery, useInfiniteApiQuery} from 'sentry/utils/queryClient';
import {apiOptions} from 'sentry/utils/api/apiOptions';

interface Params {
organization: Organization;
query: Record<string, any>;
enabled?: boolean;
query: Record<string, unknown>;
}

export function useOrganizationFlagLog({
organization,
query,
enabled: enabledParam = true,
}: Params) {
export function organizationFlagLogOptions({organization, query}: Params) {
// Don't make the request if start = end. The backend returns 400 but we prefer an empty response.
const enabled =
(!query.start || !query.end || query.start !== query.end) && enabledParam;
const enabled = !query.start || !query.end || query.start !== query.end;

return useApiQuery<RawFlagData>(
[
getApiUrl('/organizations/$organizationIdOrSlug/flags/logs/', {
path: {
organizationIdOrSlug: organization.slug,
},
}),
{query},
],
return apiOptions.as<RawFlagData>()(
'/organizations/$organizationIdOrSlug/flags/logs/',
{
path: enabled ? {organizationIdOrSlug: organization.slug} : skipToken,
query,
staleTime: 0,
enabled,
}
);
}
Expand All @@ -48,39 +36,42 @@ export function useOrganizationFlagLogInfinite({
query,
enabled: enabledParam = true,
maxPages = 10,
}: InfiniteParams) {
}: InfiniteParams & {enabled?: boolean}) {
// Don't make the request if start = end. The backend returns 400 but we prefer an empty response.
const enabled =
(!query.start || !query.end || query.start !== query.end) && enabledParam;

const apiQuery = useInfiniteApiQuery<RawFlagData>({
queryKey: [
{infinite: true, version: 'v1'},
getApiUrl('/organizations/$organizationIdOrSlug/flags/logs/', {
path: {
organizationIdOrSlug: organization.slug,
},
}),
{query},
],
staleTime: 0,
enabled,
const {
data: infiniteData,
isFetching,
hasNextPage,
fetchNextPage,
isPending,
isError,
error,
} = useInfiniteQuery({
...apiOptions.asInfinite<RawFlagData>()(
'/organizations/$organizationIdOrSlug/flags/logs/',
{
path: enabled ? {organizationIdOrSlug: organization.slug} : skipToken,
query,
staleTime: 0,
}
),
});

const currentNumberPages = apiQuery.data?.pages.length ?? 0;
const currentNumberPages = infiniteData?.pages.length ?? 0;

useEffect(() => {
if (
!apiQuery.isFetching &&
apiQuery.hasNextPage &&
currentNumberPages + 1 < maxPages
) {
apiQuery.fetchNextPage();
if (!isFetching && hasNextPage && currentNumberPages + 1 < maxPages) {
fetchNextPage();
}
}, [apiQuery, maxPages, currentNumberPages]);
}, [isFetching, hasNextPage, fetchNextPage, maxPages, currentNumberPages]);

return {
...apiQuery,
data: apiQuery.data?.pages.flatMap(([pageData]) => pageData.data),
data: infiniteData?.pages.flatMap(page => page.json.data),
isPending,
isError,
error,
};
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {useEffect, useMemo} from 'react';
import {useQuery} from '@tanstack/react-query';
import intersection from 'lodash/intersection';
import moment from 'moment-timezone';

import {organizationFlagLogOptions} from 'sentry/components/featureFlags/hooks/useOrganizationFlagLog';
import {
hydrateToFlagSeries,
type RawFlag,
Expand All @@ -11,9 +13,6 @@ import type {Event} from 'sentry/types/event';
import type {Organization} from 'sentry/types/organization';
import {defined} from 'sentry/utils';
import {trackAnalytics} from 'sentry/utils/analytics';
import {getApiUrl} from 'sentry/utils/api/getApiUrl';
import {useApiQuery, type UseApiQueryResult} from 'sentry/utils/queryClient';
import type {RequestError} from 'sentry/utils/requestError/requestError';

/**
* Legacy suspect flags implementation.
Expand All @@ -33,7 +32,13 @@ export function useLegacyEventSuspectFlags({
firstSeen: string;
organization: Organization;
rawFlagData: RawFlagData | undefined;
}): UseApiQueryResult<RawFlagData, RequestError> & {suspectFlags: RawFlag[]} {
}): {
data: RawFlagData | undefined;
error: Error | null;
isError: boolean;
isPending: boolean;
suspectFlags: RawFlag[];
} {
const hydratedFlagData = hydrateToFlagSeries(rawFlagData?.data ?? []);

// map flag data to arrays of flag names
Expand All @@ -48,28 +53,19 @@ export function useLegacyEventSuspectFlags({

// get all the audit log flag changes which happened prior to the first seen date
const start = moment(firstSeen).subtract(1, 'year').format('YYYY-MM-DD HH:mm:ss');
const apiQueryResponse = useApiQuery<RawFlagData>(
[
getApiUrl('/organizations/$organizationIdOrSlug/flags/logs/', {
path: {organizationIdOrSlug: organization.slug},
}),
{
query: {
flag: intersectionFlags,
start,
end: firstSeen,
statsPeriod: undefined,
},
const {data, isError, isPending, error} = useQuery({
...organizationFlagLogOptions({
organization,
query: {
flag: intersectionFlags,
start,
end: firstSeen,
statsPeriod: undefined,
},
],
{
staleTime: 0,
// if no intersection, then there are no suspect flags
enabled: enabled && Boolean(intersectionFlags.length),
}
);

const {data, isError, isPending} = apiQueryResponse;
}),
// if no intersection, then there are no suspect flags
enabled: enabled && Boolean(intersectionFlags.length),
});

// no flags in common between event evaluations and audit log
// only track this analytic if there is at least 1 flag recorded
Expand Down Expand Up @@ -131,5 +127,5 @@ export function useLegacyEventSuspectFlags({
organization,
]);

return {...apiQueryResponse, suspectFlags};
return {data, isError, isPending, error, suspectFlags};
}
Loading
Loading