diff --git a/frontend/web/components/pages/features/FeaturesPage.tsx b/frontend/web/components/pages/features/FeaturesPage.tsx index 6bcec90d8b30..746cc58b80d9 100644 --- a/frontend/web/components/pages/features/FeaturesPage.tsx +++ b/frontend/web/components/pages/features/FeaturesPage.tsx @@ -14,6 +14,7 @@ import FeatureRowSkeleton from 'components/feature-summary/FeatureRowSkeleton' import JSONReference from 'components/JSONReference' import Permission from 'common/providers/Permission' import { + FeaturePermalinkHandler, FeaturesEmptyState, FeatureMetricsSection, FeaturesPageHeader, @@ -174,6 +175,22 @@ const FeaturesPage: FC = ({ [data?.pagination], ) + // A permalinked feature (`?feature=`) is opened by its own FeatureRow, but + // rows only exist for the current page. When the target feature lives on a later + // page we open it via a dedicated handler instead (see #4239). + const permalinkFeatureId = useMemo(() => { + const { feature } = Utils.fromParam(history.location.search) as { + feature?: string + } + const featureId = feature ? parseInt(feature) : NaN + return Number.isNaN(featureId) ? null : featureId + }, [history.location.search]) + const isPermalinkOnCurrentPage = + permalinkFeatureId !== null && + projectFlags.some( + (projectFlag: ProjectFlag) => projectFlag.id === permalinkFeatureId, + ) + usePageTracking({ context: { environmentId, @@ -378,6 +395,20 @@ const FeaturesPage: FC = ({ {renderFeaturesList()} + {!!data && + !isPermalinkOnCurrentPage && + permalinkFeatureId !== null && + !!currentEnvironment && ( + + )} + `) that is not + * present on the current page of results. + * + * `FeatureRow` opens the panel from its own effect when the feature id in the URL + * matches its feature, but rows are only rendered for the current page. For a + * feature on a later page no row exists, so the permalink would otherwise be a + * no-op and the user would simply land on the first page (see #4239). + * + * Here we fetch the feature (and its environment feature state) directly and render + * a single hidden `FeatureRow`, so the exact same panel-opening logic runs without + * having to duplicate it. + */ +const FeaturePermalinkHandler: FC = ({ + environmentApiKey, + environmentId, + experimentMode, + featureId, + minimumChangeRequestApprovals, + projectId, +}) => { + const { data: projectFlag } = useGetProjectFlagQuery({ + id: featureId, + project: projectId, + }) + const { data: featureStates } = useGetFeatureStatesQuery({ + environment: environmentId, + feature: featureId, + }) + + const environmentFlags = useMemo(() => { + const environmentFeatureState = featureStates?.results?.find( + (featureState) => !featureState.feature_segment && !featureState.identity, + ) + return environmentFeatureState + ? { [featureId]: environmentFeatureState } + : {} + }, [featureStates, featureId]) + + if (!projectFlag) { + return null + } + + return ( +
+ + {({ permission }) => ( + + )} + +
+ ) +} + +export default FeaturePermalinkHandler diff --git a/frontend/web/components/pages/features/components/index.ts b/frontend/web/components/pages/features/components/index.ts index deceb0bbe1e3..673db728d2a8 100644 --- a/frontend/web/components/pages/features/components/index.ts +++ b/frontend/web/components/pages/features/components/index.ts @@ -1,3 +1,4 @@ +export { default as FeaturePermalinkHandler } from './FeaturePermalinkHandler' export { FeaturesEmptyState } from './FeaturesEmptyState' export { FeatureMetricsSection } from './FeatureMetricsSection' export { FeaturesPageHeader } from './FeaturesPageHeader'