mirror of
https://github.com/datahub-project/datahub.git
synced 2025-10-18 12:27:15 +00:00
fix(ui): open links from inside modal in a new tab (#14431)
This commit is contained in:
parent
4e83f951be
commit
8202977c74
@ -3,6 +3,8 @@ import { Modal as AntModal, ModalProps as AntModalProps } from 'antd';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { ModalContext } from '@app/sharedV2/modals/ModalContext';
|
||||
|
||||
const StyledModal = styled(AntModal)<{ hasChildren: boolean }>`
|
||||
font-family: ${typography.fonts.body};
|
||||
|
||||
@ -125,7 +127,7 @@ export function Modal({
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ModalContext.Provider value={{ isInsideModal: true }}>{children}</ModalContext.Provider>
|
||||
</StyledModal>
|
||||
);
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import styled from 'styled-components';
|
||||
|
||||
import analytics, { EventType } from '@app/analytics';
|
||||
import AutoCompleteEntityItem from '@app/searchV2/autoCompleteV2/AutoCompleteEntityItem';
|
||||
import { useGetModalLinkProps } from '@app/sharedV2/modals/useGetModalLinkProps';
|
||||
import { useEntityRegistryV2 } from '@app/useEntityRegistry';
|
||||
|
||||
import { DataHubPageModuleType, Entity } from '@types';
|
||||
@ -34,6 +35,7 @@ export default function EntityItem({
|
||||
padding,
|
||||
}: Props) {
|
||||
const entityRegistry = useEntityRegistryV2();
|
||||
const linkProps = useGetModalLinkProps();
|
||||
|
||||
const sendAnalytics = useCallback(
|
||||
() =>
|
||||
@ -60,7 +62,11 @@ export default function EntityItem({
|
||||
onClick={sendAnalytics}
|
||||
/>
|
||||
) : (
|
||||
<StyledLink to={entityRegistry.getEntityUrl(entity.type, entity.urn)} onClick={sendAnalytics}>
|
||||
<StyledLink
|
||||
to={entityRegistry.getEntityUrl(entity.type, entity.urn)}
|
||||
onClick={sendAnalytics}
|
||||
{...linkProps}
|
||||
>
|
||||
<AutoCompleteEntityItem
|
||||
entity={entity}
|
||||
key={entity.urn}
|
||||
|
@ -12,6 +12,7 @@ import EntitySubtitle from '@app/searchV2/autoCompleteV2/components/subtitle/Ent
|
||||
import { VARIANT_STYLES } from '@app/searchV2/autoCompleteV2/constants';
|
||||
import { EntityItemVariant } from '@app/searchV2/autoCompleteV2/types';
|
||||
import { getEntityDisplayType } from '@app/searchV2/autoCompleteV2/utils';
|
||||
import { useGetModalLinkProps } from '@app/sharedV2/modals/useGetModalLinkProps';
|
||||
import { Text } from '@src/alchemy-components';
|
||||
import { useEntityRegistryV2 } from '@src/app/useEntityRegistry';
|
||||
import { Entity, MatchedField } from '@src/types.generated';
|
||||
@ -131,6 +132,8 @@ export default function AutoCompleteEntityItem({
|
||||
}: EntityAutocompleteItemProps) {
|
||||
const theme = useTheme();
|
||||
const entityRegistry = useEntityRegistryV2();
|
||||
const linkProps = useGetModalLinkProps();
|
||||
|
||||
const displayName = entityRegistry.getDisplayName(entity.type, entity);
|
||||
const displayType = getEntityDisplayType(entity, entityRegistry);
|
||||
const variantProps = VARIANT_STYLES.get(variant ?? 'default');
|
||||
@ -140,7 +143,7 @@ export default function AutoCompleteEntityItem({
|
||||
: DisplayNameHoverFromContainer;
|
||||
|
||||
const displayNameContent = variantProps?.nameCanBeHovered ? (
|
||||
<Link to={entityRegistry.getEntityUrl(entity.type, entity.urn)}>
|
||||
<Link to={entityRegistry.getEntityUrl(entity.type, entity.urn)} {...linkProps}>
|
||||
<DisplayNameHoverComponent
|
||||
displayName={displayName}
|
||||
highlight={query}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { useModalContext } from '@app/sharedV2/modals/ModalContext';
|
||||
import { PageRoutes } from '@conf/Global';
|
||||
|
||||
// Function to check if the current page is an embedded profile
|
||||
@ -11,5 +12,9 @@ export const useIsEmbeddedProfile = () => {
|
||||
|
||||
export const useEmbeddedProfileLinkProps = () => {
|
||||
const isEmbedded = useIsEmbeddedProfile();
|
||||
return useMemo(() => (isEmbedded ? { target: '_blank', rel: 'noreferrer noopener' } : {}), [isEmbedded]);
|
||||
const { isInsideModal } = useModalContext(); // If link is opened from inside a modal
|
||||
return useMemo(
|
||||
() => (isEmbedded || isInsideModal ? { target: '_blank', rel: 'noreferrer noopener' } : {}),
|
||||
[isEmbedded, isInsideModal],
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,9 @@
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
export type ModalContextType = {
|
||||
isInsideModal: boolean;
|
||||
};
|
||||
|
||||
export const ModalContext = createContext<ModalContextType>({ isInsideModal: false });
|
||||
|
||||
export const useModalContext = () => useContext(ModalContext);
|
@ -0,0 +1,83 @@
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { ModalContext, ModalContextType } from '@app/sharedV2/modals/ModalContext';
|
||||
import { useGetModalLinkProps } from '@app/sharedV2/modals/useGetModalLinkProps';
|
||||
|
||||
function wrapperWithContext(value: ModalContextType) {
|
||||
return ({ children }: { children: React.ReactNode }) => (
|
||||
<ModalContext.Provider value={value}>{children}</ModalContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
describe('useGetModalLinkProps', () => {
|
||||
it('should return an empty object when not in modal (default context)', () => {
|
||||
const { result } = renderHook(() => useGetModalLinkProps());
|
||||
expect(result.current).toEqual({});
|
||||
});
|
||||
|
||||
it('should return correct props when isInsideModal = true', () => {
|
||||
const { result } = renderHook(() => useGetModalLinkProps(), {
|
||||
wrapper: wrapperWithContext({ isInsideModal: true }),
|
||||
});
|
||||
expect(result.current).toEqual({
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return empty object when isInsideModal = false', () => {
|
||||
const { result } = renderHook(() => useGetModalLinkProps(), {
|
||||
wrapper: wrapperWithContext({ isInsideModal: false }),
|
||||
});
|
||||
expect(result.current).toEqual({});
|
||||
});
|
||||
|
||||
it('should memoize result for same context value', () => {
|
||||
const { result, rerender } = renderHook(() => useGetModalLinkProps(), {
|
||||
wrapper: wrapperWithContext({ isInsideModal: true }),
|
||||
});
|
||||
const first = result.current;
|
||||
rerender();
|
||||
expect(result.current).toBe(first); // stable reference
|
||||
});
|
||||
|
||||
it('should change reference when context value changes', () => {
|
||||
const wrapper = ({ children, value }: PropsWithChildren<{ value: boolean }>) => (
|
||||
<ModalContext.Provider value={{ isInsideModal: value }}>{children}</ModalContext.Provider>
|
||||
);
|
||||
|
||||
const { result, rerender } = renderHook(() => useGetModalLinkProps(), {
|
||||
initialProps: { value: false },
|
||||
wrapper,
|
||||
});
|
||||
|
||||
const first = result.current;
|
||||
rerender({ value: true });
|
||||
expect(result.current).not.toBe(first);
|
||||
expect(result.current).toEqual({
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer',
|
||||
});
|
||||
});
|
||||
|
||||
it('should have no extra keys when not in modal', () => {
|
||||
const { result } = renderHook(() => useGetModalLinkProps(), {
|
||||
wrapper: wrapperWithContext({ isInsideModal: false }),
|
||||
});
|
||||
expect(Object.keys(result.current)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should match snapshot when in modal', () => {
|
||||
const { result } = renderHook(() => useGetModalLinkProps(), {
|
||||
wrapper: wrapperWithContext({ isInsideModal: true }),
|
||||
});
|
||||
expect(result.current).toMatchInlineSnapshot(`
|
||||
{
|
||||
"rel": "noopener noreferrer",
|
||||
"target": "_blank",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
@ -0,0 +1,14 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { useModalContext } from '@app/sharedV2/modals/ModalContext';
|
||||
|
||||
export const useGetModalLinkProps = () => {
|
||||
const { isInsideModal } = useModalContext();
|
||||
|
||||
return useMemo(() => {
|
||||
if (isInsideModal) {
|
||||
return { target: '_blank', rel: 'noopener noreferrer' };
|
||||
}
|
||||
return {};
|
||||
}, [isInsideModal]);
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user