mirror of
				https://github.com/datahub-project/datahub.git
				synced 2025-11-04 04:39:10 +00:00 
			
		
		
		
	feat(homepage): add reset to default template functionality and update home page settings option (#14202)
This commit is contained in:
		
							parent
							
								
									bcee2a4fb7
								
							
						
					
					
						commit
						b1ef86b83e
					
				@ -102,6 +102,9 @@ public class MeResolver implements DataFetcher<CompletableFuture<AuthenticatedUs
 | 
			
		||||
            platformPrivileges.setManageApplications(
 | 
			
		||||
                ApplicationAuthorizationUtils.canManageApplications(context));
 | 
			
		||||
            platformPrivileges.setManageFeatures(AuthorizationUtils.canManageFeatures(context));
 | 
			
		||||
            platformPrivileges.setManageHomePageTemplates(
 | 
			
		||||
                AuthorizationUtils.canManageHomePageTemplates(context));
 | 
			
		||||
 | 
			
		||||
            // Construct and return authenticated user object.
 | 
			
		||||
            final AuthenticatedUser authUser = new AuthenticatedUser();
 | 
			
		||||
            authUser.setCorpUser(corpUser);
 | 
			
		||||
 | 
			
		||||
@ -86,7 +86,16 @@ public class UpdateUserHomePageSettingsResolver implements DataFetcher<Completab
 | 
			
		||||
      @Nonnull final CorpUserHomePageSettings settings,
 | 
			
		||||
      @Nonnull final UpdateUserHomePageSettingsInput input) {
 | 
			
		||||
 | 
			
		||||
    if (input.getPageTemplate() != null) {
 | 
			
		||||
    Boolean shouldRemoveTemplate = input.getRemovePageTemplate();
 | 
			
		||||
 | 
			
		||||
    if (input.getPageTemplate() != null && Boolean.TRUE.equals(shouldRemoveTemplate)) {
 | 
			
		||||
      throw new IllegalArgumentException(
 | 
			
		||||
          "Invalid inputs: Cannot specify both pageTemplate and removePageTemplate.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (Boolean.TRUE.equals(shouldRemoveTemplate)) {
 | 
			
		||||
      settings.data().remove("pageTemplate");
 | 
			
		||||
    } else if (input.getPageTemplate() != null) {
 | 
			
		||||
      settings.setPageTemplate(UrnUtils.getUrn(input.getPageTemplate()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -169,9 +169,11 @@ public class CorpUserMapper {
 | 
			
		||||
      @Nonnull final com.linkedin.identity.CorpUserHomePageSettings homePageSettings) {
 | 
			
		||||
    CorpUserHomePageSettings result = new CorpUserHomePageSettings();
 | 
			
		||||
 | 
			
		||||
    if (homePageSettings.hasPageTemplate()) {
 | 
			
		||||
    if (homePageSettings.getPageTemplate() != null) {
 | 
			
		||||
      result.setPageTemplate(
 | 
			
		||||
          (DataHubPageTemplate) UrnToEntityMapper.map(null, homePageSettings.getPageTemplate()));
 | 
			
		||||
    } else {
 | 
			
		||||
      result.setPageTemplate(null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (homePageSettings.hasDismissedAnnouncements()) {
 | 
			
		||||
 | 
			
		||||
@ -192,6 +192,11 @@ type PlatformPrivileges {
 | 
			
		||||
  Whether the user can manage platform features.
 | 
			
		||||
  """
 | 
			
		||||
  manageFeatures: Boolean!
 | 
			
		||||
 | 
			
		||||
  """
 | 
			
		||||
  Whether the user can manage default home page template.
 | 
			
		||||
  """
 | 
			
		||||
  manageHomePageTemplates: Boolean!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
@ -12503,6 +12503,11 @@ input UpdateUserHomePageSettingsInput {
 | 
			
		||||
  The list of urns of announcement posts dismissed by the user.
 | 
			
		||||
  """
 | 
			
		||||
  newDismissedAnnouncements: [String]
 | 
			
		||||
 | 
			
		||||
  """
 | 
			
		||||
  Whether to remove the page template for the user.
 | 
			
		||||
  """
 | 
			
		||||
  removePageTemplate: Boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
@ -3741,6 +3741,7 @@ export const mocks = [
 | 
			
		||||
                        viewStructuredPropertiesPage: true,
 | 
			
		||||
                        manageApplications: true,
 | 
			
		||||
                        manageFeatures: true,
 | 
			
		||||
                        manageHomePageTemplates: true,
 | 
			
		||||
                    },
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
@ -4027,6 +4028,7 @@ export const platformPrivileges: PlatformPrivileges = {
 | 
			
		||||
    viewStructuredPropertiesPage: true,
 | 
			
		||||
    manageApplications: true,
 | 
			
		||||
    manageFeatures: true,
 | 
			
		||||
    manageHomePageTemplates: true,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const DomainMock1 = {
 | 
			
		||||
 | 
			
		||||
@ -89,6 +89,7 @@ describe('handleAccessRoles', () => {
 | 
			
		||||
                    viewStructuredPropertiesPage: true,
 | 
			
		||||
                    manageApplications: true,
 | 
			
		||||
                    manageFeatures: true,
 | 
			
		||||
                    manageHomePageTemplates: true,
 | 
			
		||||
                    __typename: 'PlatformPrivileges',
 | 
			
		||||
                },
 | 
			
		||||
                __typename: 'AuthenticatedUser',
 | 
			
		||||
@ -174,6 +175,7 @@ describe('handleAccessRoles', () => {
 | 
			
		||||
                    viewStructuredPropertiesPage: true,
 | 
			
		||||
                    manageApplications: true,
 | 
			
		||||
                    manageFeatures: true,
 | 
			
		||||
                    manageHomePageTemplates: true,
 | 
			
		||||
                    __typename: 'PlatformPrivileges',
 | 
			
		||||
                },
 | 
			
		||||
                __typename: 'AuthenticatedUser',
 | 
			
		||||
@ -267,6 +269,7 @@ describe('handleAccessRoles', () => {
 | 
			
		||||
                    viewStructuredPropertiesPage: true,
 | 
			
		||||
                    manageApplications: true,
 | 
			
		||||
                    manageFeatures: true,
 | 
			
		||||
                    manageHomePageTemplates: true,
 | 
			
		||||
                    __typename: 'PlatformPrivileges',
 | 
			
		||||
                },
 | 
			
		||||
                __typename: 'AuthenticatedUser',
 | 
			
		||||
 | 
			
		||||
@ -1,38 +0,0 @@
 | 
			
		||||
import { Button, Tooltip } from '@components';
 | 
			
		||||
import React, { useCallback } from 'react';
 | 
			
		||||
import styled from 'styled-components';
 | 
			
		||||
 | 
			
		||||
import analytics, { EventType } from '@app/analytics';
 | 
			
		||||
import { usePageTemplateContext } from '@app/homeV3/context/PageTemplateContext';
 | 
			
		||||
 | 
			
		||||
const ButtonWrapper = styled.div`
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: flex-end;
 | 
			
		||||
    margin-right: 42px;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export default function EditDefaultTemplateButton() {
 | 
			
		||||
    const { setIsEditingGlobalTemplate, isEditingGlobalTemplate } = usePageTemplateContext();
 | 
			
		||||
 | 
			
		||||
    const onClick = useCallback(() => {
 | 
			
		||||
        setIsEditingGlobalTemplate(true);
 | 
			
		||||
        analytics.event({
 | 
			
		||||
            type: EventType.HomePageTemplateGlobalTemplateEditingStart,
 | 
			
		||||
        });
 | 
			
		||||
    }, [setIsEditingGlobalTemplate]);
 | 
			
		||||
 | 
			
		||||
    // TODO: also hide this if you don't have permissions - CH-510
 | 
			
		||||
    if (isEditingGlobalTemplate) return null;
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <ButtonWrapper>
 | 
			
		||||
            <Tooltip title="Edit the home page that users see by default">
 | 
			
		||||
                <Button
 | 
			
		||||
                    icon={{ icon: 'PencilSimpleLine', color: 'gray', source: 'phosphor' }}
 | 
			
		||||
                    variant="text"
 | 
			
		||||
                    onClick={onClick}
 | 
			
		||||
                />
 | 
			
		||||
            </Tooltip>
 | 
			
		||||
        </ButtonWrapper>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
@ -1,8 +1,8 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
 | 
			
		||||
import EditDefaultTemplateBar from '@app/homeV3/EditDefaultTemplateBar';
 | 
			
		||||
import EditDefaultTemplateButton from '@app/homeV3/EditDefaultTemplateButton';
 | 
			
		||||
import { Announcements } from '@app/homeV3/announcements/Announcements';
 | 
			
		||||
import EditDefaultTemplateBar from '@app/homeV3/settings/EditDefaultTemplateBar';
 | 
			
		||||
import EditHomePageSettingsButton from '@app/homeV3/settings/EditHomePageSettingsButton';
 | 
			
		||||
import { CenteredContainer, ContentContainer, ContentDiv } from '@app/homeV3/styledComponents';
 | 
			
		||||
import Template from '@app/homeV3/template/Template';
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@ const HomePageContent = () => {
 | 
			
		||||
        <ContentContainer>
 | 
			
		||||
            <CenteredContainer>
 | 
			
		||||
                <ContentDiv>
 | 
			
		||||
                    <EditDefaultTemplateButton />
 | 
			
		||||
                    <EditHomePageSettingsButton />
 | 
			
		||||
                    <Announcements />
 | 
			
		||||
                    <Template />
 | 
			
		||||
                    <EditDefaultTemplateBar />
 | 
			
		||||
 | 
			
		||||
@ -22,7 +22,8 @@ export const PageTemplateProvider = ({ children }: { children: ReactNode }) => {
 | 
			
		||||
    } = useTemplateState();
 | 
			
		||||
 | 
			
		||||
    // Template operations
 | 
			
		||||
    const { updateTemplateWithModule, removeModuleFromTemplate, upsertTemplate } = useTemplateOperations();
 | 
			
		||||
    const { updateTemplateWithModule, removeModuleFromTemplate, upsertTemplate, resetTemplateToDefault } =
 | 
			
		||||
        useTemplateOperations(setPersonalTemplate);
 | 
			
		||||
 | 
			
		||||
    // Modal state
 | 
			
		||||
    const moduleModalState = useModuleModalState();
 | 
			
		||||
@ -56,6 +57,7 @@ export const PageTemplateProvider = ({ children }: { children: ReactNode }) => {
 | 
			
		||||
            upsertModule,
 | 
			
		||||
            moduleModalState,
 | 
			
		||||
            moveModule,
 | 
			
		||||
            resetTemplateToDefault,
 | 
			
		||||
        }),
 | 
			
		||||
        [
 | 
			
		||||
            personalTemplate,
 | 
			
		||||
@ -71,6 +73,7 @@ export const PageTemplateProvider = ({ children }: { children: ReactNode }) => {
 | 
			
		||||
            upsertModule,
 | 
			
		||||
            moduleModalState,
 | 
			
		||||
            moveModule,
 | 
			
		||||
            resetTemplateToDefault,
 | 
			
		||||
        ],
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -92,6 +92,7 @@ const mockMoveModule = vi.fn();
 | 
			
		||||
const mockUpdateTemplateWithModule = vi.fn();
 | 
			
		||||
const mockRemoveModuleFromTemplate = vi.fn();
 | 
			
		||||
const mockUpsertTemplate = vi.fn();
 | 
			
		||||
const mockResetTemplateToDefault = vi.fn();
 | 
			
		||||
 | 
			
		||||
describe('PageTemplateContext', () => {
 | 
			
		||||
    beforeEach(() => {
 | 
			
		||||
@ -113,6 +114,7 @@ describe('PageTemplateContext', () => {
 | 
			
		||||
            updateTemplateWithModule: mockUpdateTemplateWithModule,
 | 
			
		||||
            removeModuleFromTemplate: mockRemoveModuleFromTemplate,
 | 
			
		||||
            upsertTemplate: mockUpsertTemplate,
 | 
			
		||||
            resetTemplateToDefault: mockResetTemplateToDefault,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        mockUseModuleOperations.mockReturnValue({
 | 
			
		||||
 | 
			
		||||
@ -52,6 +52,8 @@ const mockModule: PageModuleFragment = {
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const setPersonalTemplate = vi.fn();
 | 
			
		||||
 | 
			
		||||
describe('useTemplateOperations', () => {
 | 
			
		||||
    beforeEach(() => {
 | 
			
		||||
        vi.clearAllMocks();
 | 
			
		||||
@ -61,7 +63,7 @@ describe('useTemplateOperations', () => {
 | 
			
		||||
 | 
			
		||||
    describe('updateTemplateWithModule', () => {
 | 
			
		||||
        it('should add module to new row when rowIndex is undefined', () => {
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations());
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
 | 
			
		||||
 | 
			
		||||
            const position: ModulePositionInput = {
 | 
			
		||||
                rowIndex: undefined,
 | 
			
		||||
@ -77,7 +79,7 @@ describe('useTemplateOperations', () => {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should add module to existing row on the left side', () => {
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations());
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
 | 
			
		||||
 | 
			
		||||
            const position: ModulePositionInput = {
 | 
			
		||||
                rowIndex: 0,
 | 
			
		||||
@ -94,7 +96,7 @@ describe('useTemplateOperations', () => {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should add module to existing row on the right side', () => {
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations());
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
 | 
			
		||||
 | 
			
		||||
            const position: ModulePositionInput = {
 | 
			
		||||
                rowIndex: 0,
 | 
			
		||||
@ -111,7 +113,7 @@ describe('useTemplateOperations', () => {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should create new row when rowIndex is out of bounds', () => {
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations());
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
 | 
			
		||||
 | 
			
		||||
            const position: ModulePositionInput = {
 | 
			
		||||
                rowIndex: 5,
 | 
			
		||||
@ -127,7 +129,7 @@ describe('useTemplateOperations', () => {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should handle template with no rows', () => {
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations());
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
 | 
			
		||||
 | 
			
		||||
            const templateWithoutRows: PageTemplateFragment = {
 | 
			
		||||
                urn: 'urn:li:pageTemplate:empty',
 | 
			
		||||
@ -162,7 +164,7 @@ describe('useTemplateOperations', () => {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should return null when template is null', () => {
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations());
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
 | 
			
		||||
 | 
			
		||||
            const position: ModulePositionInput = {
 | 
			
		||||
                rowIndex: 0,
 | 
			
		||||
@ -177,7 +179,7 @@ describe('useTemplateOperations', () => {
 | 
			
		||||
 | 
			
		||||
    describe('removeModuleFromTemplate', () => {
 | 
			
		||||
        it('should remove module by moduleIndex when provided', () => {
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations());
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
 | 
			
		||||
 | 
			
		||||
            const templateWithMultipleModules: PageTemplateFragment = {
 | 
			
		||||
                ...mockTemplate,
 | 
			
		||||
@ -231,7 +233,7 @@ describe('useTemplateOperations', () => {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should handle duplicate URNs correctly with moduleIndex', () => {
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations());
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
 | 
			
		||||
 | 
			
		||||
            const templateWithDuplicateUrns: PageTemplateFragment = {
 | 
			
		||||
                ...mockTemplate,
 | 
			
		||||
@ -297,7 +299,7 @@ describe('useTemplateOperations', () => {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should fall back to URN search when moduleIndex is invalid', () => {
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations());
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
 | 
			
		||||
 | 
			
		||||
            const templateWithMultipleModules: PageTemplateFragment = {
 | 
			
		||||
                ...mockTemplate,
 | 
			
		||||
@ -351,7 +353,7 @@ describe('useTemplateOperations', () => {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should fall back to URN search when moduleIndex URN does not match', () => {
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations());
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
 | 
			
		||||
 | 
			
		||||
            const templateWithMultipleModules: PageTemplateFragment = {
 | 
			
		||||
                ...mockTemplate,
 | 
			
		||||
@ -405,7 +407,7 @@ describe('useTemplateOperations', () => {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should remove entire row when last module is removed', () => {
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations());
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
 | 
			
		||||
 | 
			
		||||
            const templateWithSingleModule: PageTemplateFragment = {
 | 
			
		||||
                ...mockTemplate,
 | 
			
		||||
@ -462,7 +464,7 @@ describe('useTemplateOperations', () => {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should return original template when module is not found', () => {
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations());
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
 | 
			
		||||
 | 
			
		||||
            const position: ModulePositionInput = {
 | 
			
		||||
                rowIndex: 0,
 | 
			
		||||
@ -480,7 +482,7 @@ describe('useTemplateOperations', () => {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should return original template when rowIndex is invalid', () => {
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations());
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
 | 
			
		||||
 | 
			
		||||
            const position: ModulePositionInput = {
 | 
			
		||||
                rowIndex: 5, // Invalid index
 | 
			
		||||
@ -498,7 +500,7 @@ describe('useTemplateOperations', () => {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should return original template when rowIndex is undefined', () => {
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations());
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
 | 
			
		||||
 | 
			
		||||
            const position: ModulePositionInput = {
 | 
			
		||||
                rowIndex: undefined,
 | 
			
		||||
@ -516,7 +518,7 @@ describe('useTemplateOperations', () => {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should return original template when template is null', () => {
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations());
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
 | 
			
		||||
 | 
			
		||||
            const position: ModulePositionInput = {
 | 
			
		||||
                rowIndex: 0,
 | 
			
		||||
@ -530,7 +532,7 @@ describe('useTemplateOperations', () => {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should handle edge case with no modules in row', () => {
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations());
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
 | 
			
		||||
 | 
			
		||||
            const templateWithEmptyRow: PageTemplateFragment = {
 | 
			
		||||
                ...mockTemplate,
 | 
			
		||||
@ -562,7 +564,7 @@ describe('useTemplateOperations', () => {
 | 
			
		||||
 | 
			
		||||
    describe('upsertTemplate', () => {
 | 
			
		||||
        it('should upsert personal template with correct input', async () => {
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations());
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
 | 
			
		||||
 | 
			
		||||
            mockUpsertPageTemplateMutation.mockResolvedValue({
 | 
			
		||||
                data: {
 | 
			
		||||
@ -593,7 +595,7 @@ describe('useTemplateOperations', () => {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should upsert existing personal template with URN', async () => {
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations());
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
 | 
			
		||||
 | 
			
		||||
            mockUpsertPageTemplateMutation.mockResolvedValue({
 | 
			
		||||
                data: {
 | 
			
		||||
@ -624,7 +626,7 @@ describe('useTemplateOperations', () => {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should upsert global template with correct input', async () => {
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations());
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
 | 
			
		||||
 | 
			
		||||
            mockUpsertPageTemplateMutation.mockResolvedValue({
 | 
			
		||||
                data: {
 | 
			
		||||
@ -655,7 +657,7 @@ describe('useTemplateOperations', () => {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should update user settings when creating personal template', async () => {
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations());
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
 | 
			
		||||
 | 
			
		||||
            mockUpsertPageTemplateMutation.mockResolvedValue({
 | 
			
		||||
                data: {
 | 
			
		||||
@ -679,7 +681,7 @@ describe('useTemplateOperations', () => {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should not update user settings when updating existing personal template', async () => {
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations());
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
 | 
			
		||||
 | 
			
		||||
            mockUpsertPageTemplateMutation.mockResolvedValue({
 | 
			
		||||
                data: {
 | 
			
		||||
@ -697,7 +699,7 @@ describe('useTemplateOperations', () => {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should handle template with empty rows', async () => {
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations());
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
 | 
			
		||||
 | 
			
		||||
            const templateWithEmptyRows: PageTemplateFragment = {
 | 
			
		||||
                urn: 'urn:li:pageTemplate:empty',
 | 
			
		||||
@ -742,7 +744,7 @@ describe('useTemplateOperations', () => {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should handle mutation error', async () => {
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations());
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
 | 
			
		||||
 | 
			
		||||
            const error = new Error('Mutation failed');
 | 
			
		||||
            mockUpsertPageTemplateMutation.mockRejectedValue(error);
 | 
			
		||||
@ -750,4 +752,61 @@ describe('useTemplateOperations', () => {
 | 
			
		||||
            await expect(result.current.upsertTemplate(mockTemplate, true, null)).rejects.toThrow('Mutation failed');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('resetTemplateToDefault', () => {
 | 
			
		||||
        it('should call setPersonalTemplate with null', () => {
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
 | 
			
		||||
 | 
			
		||||
            act(() => {
 | 
			
		||||
                result.current.resetTemplateToDefault();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            expect(setPersonalTemplate).toHaveBeenCalledWith(null);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should call updateUserHomePageSettingsMutation with pageTemplate: null', () => {
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
 | 
			
		||||
 | 
			
		||||
            act(() => {
 | 
			
		||||
                result.current.resetTemplateToDefault();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            expect(mockUpdateUserHomePageSettings).toHaveBeenCalledWith({
 | 
			
		||||
                variables: {
 | 
			
		||||
                    input: {
 | 
			
		||||
                        removePageTemplate: true,
 | 
			
		||||
                    },
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should call setPersonalTemplate and mutation exactly once on multiple calls', () => {
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
 | 
			
		||||
 | 
			
		||||
            act(() => {
 | 
			
		||||
                result.current.resetTemplateToDefault();
 | 
			
		||||
                result.current.resetTemplateToDefault();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            expect(setPersonalTemplate).toHaveBeenCalledTimes(2);
 | 
			
		||||
            expect(mockUpdateUserHomePageSettings).toHaveBeenCalledTimes(2);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should handle async mutation call correctly', async () => {
 | 
			
		||||
            const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
 | 
			
		||||
 | 
			
		||||
            await act(async () => {
 | 
			
		||||
                await result.current.resetTemplateToDefault();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            expect(setPersonalTemplate).toHaveBeenCalledWith(null);
 | 
			
		||||
            expect(mockUpdateUserHomePageSettings).toHaveBeenCalledWith({
 | 
			
		||||
                variables: {
 | 
			
		||||
                    input: {
 | 
			
		||||
                        removePageTemplate: true,
 | 
			
		||||
                    },
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -43,7 +43,7 @@ const isValidRemovalPosition = (template: PageTemplateFragment | null, position:
 | 
			
		||||
    return rowIndex !== undefined && rowIndex >= 0 && rowIndex < rows.length;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function useTemplateOperations() {
 | 
			
		||||
export function useTemplateOperations(setPersonalTemplate: (template: PageTemplateFragment | null) => void) {
 | 
			
		||||
    const [upsertPageTemplateMutation] = useUpsertPageTemplateMutation();
 | 
			
		||||
    const [updateUserHomePageSettings] = useUpdateUserHomePageSettingsMutation();
 | 
			
		||||
 | 
			
		||||
@ -194,9 +194,21 @@ export function useTemplateOperations() {
 | 
			
		||||
        [upsertPageTemplateMutation, updateUserHomePageSettings],
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const resetTemplateToDefault = () => {
 | 
			
		||||
        setPersonalTemplate(null);
 | 
			
		||||
        updateUserHomePageSettings({
 | 
			
		||||
            variables: {
 | 
			
		||||
                input: {
 | 
			
		||||
                    removePageTemplate: true,
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        updateTemplateWithModule,
 | 
			
		||||
        removeModuleFromTemplate,
 | 
			
		||||
        upsertTemplate,
 | 
			
		||||
        resetTemplateToDefault,
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -60,4 +60,5 @@ export type PageTemplateContextState = {
 | 
			
		||||
    moduleModalState: ModuleModalState;
 | 
			
		||||
    removeModule: (input: RemoveModuleInput) => void;
 | 
			
		||||
    moveModule: (input: MoveModuleInput) => void;
 | 
			
		||||
    resetTemplateToDefault: () => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,116 @@
 | 
			
		||||
import { Button, Dropdown, colors } from '@components';
 | 
			
		||||
import React, { useCallback, useState } from 'react';
 | 
			
		||||
import styled from 'styled-components';
 | 
			
		||||
 | 
			
		||||
import analytics, { EventType } from '@app/analytics';
 | 
			
		||||
import { useUserContext } from '@app/context/useUserContext';
 | 
			
		||||
import { usePageTemplateContext } from '@app/homeV3/context/PageTemplateContext';
 | 
			
		||||
import { ConfirmationModal } from '@app/sharedV2/modals/ConfirmationModal';
 | 
			
		||||
 | 
			
		||||
const ButtonWrapper = styled.div`
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    background-color: ${colors.white};
 | 
			
		||||
    height: 40px;
 | 
			
		||||
    width: 40px;
 | 
			
		||||
    position: fixed;
 | 
			
		||||
    right: 32px;
 | 
			
		||||
    bottom: 32px;
 | 
			
		||||
    border-radius: 200px;
 | 
			
		||||
    box-shadow: 0px 4px 12px 0px rgba(9, 1, 61, 0.12);
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const DropdownContainer = styled.div`
 | 
			
		||||
    border-radius: 12px;
 | 
			
		||||
    box-shadow: 0px 4px 12px 0px rgba(9, 1, 61, 0.12);
 | 
			
		||||
    background-color: white;
 | 
			
		||||
    overflow: hidden; // Cleanly rounds edges
 | 
			
		||||
 | 
			
		||||
    .ant-dropdown-menu-item {
 | 
			
		||||
        padding: 8px 16px;
 | 
			
		||||
    }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export default function EditHomePageSettingsButton() {
 | 
			
		||||
    const user = useUserContext();
 | 
			
		||||
    const canEditDefaultTemplate = user.platformPrivileges?.manageHomePageTemplates;
 | 
			
		||||
 | 
			
		||||
    const { setIsEditingGlobalTemplate, isEditingGlobalTemplate, resetTemplateToDefault, personalTemplate } =
 | 
			
		||||
        usePageTemplateContext();
 | 
			
		||||
 | 
			
		||||
    const isOnPersonalTemplate = !!personalTemplate;
 | 
			
		||||
 | 
			
		||||
    const [showConfirmResetModal, setShowConfirmResetModal] = useState(false);
 | 
			
		||||
 | 
			
		||||
    const startGlobalTemplateEdit = useCallback(() => {
 | 
			
		||||
        setIsEditingGlobalTemplate(true);
 | 
			
		||||
        analytics.event({
 | 
			
		||||
            type: EventType.HomePageTemplateGlobalTemplateEditingStart,
 | 
			
		||||
        });
 | 
			
		||||
    }, [setIsEditingGlobalTemplate]);
 | 
			
		||||
 | 
			
		||||
    const handleResetToDefault = useCallback(() => {
 | 
			
		||||
        resetTemplateToDefault();
 | 
			
		||||
        setShowConfirmResetModal(false);
 | 
			
		||||
        analytics.event({
 | 
			
		||||
            type: EventType.HomePageTemplateResetToGlobalTemplate,
 | 
			
		||||
        });
 | 
			
		||||
    }, [resetTemplateToDefault]);
 | 
			
		||||
 | 
			
		||||
    if (isEditingGlobalTemplate || (!canEditDefaultTemplate && !isOnPersonalTemplate)) return null;
 | 
			
		||||
 | 
			
		||||
    const menu = {
 | 
			
		||||
        items: [
 | 
			
		||||
            ...(canEditDefaultTemplate
 | 
			
		||||
                ? [
 | 
			
		||||
                      {
 | 
			
		||||
                          label: 'Edit Organization Default',
 | 
			
		||||
                          key: 'edit-organization-default',
 | 
			
		||||
                          style: {
 | 
			
		||||
                              color: colors.gray[600],
 | 
			
		||||
                              fontSize: '14px',
 | 
			
		||||
                          },
 | 
			
		||||
                          onClick: startGlobalTemplateEdit,
 | 
			
		||||
                      },
 | 
			
		||||
                  ]
 | 
			
		||||
                : []),
 | 
			
		||||
            ...(isOnPersonalTemplate
 | 
			
		||||
                ? [
 | 
			
		||||
                      {
 | 
			
		||||
                          label: 'Reset to Organization Default',
 | 
			
		||||
                          key: 'reset-to-organization-default',
 | 
			
		||||
                          style: {
 | 
			
		||||
                              color: colors.red[1000],
 | 
			
		||||
                              fontSize: '14px',
 | 
			
		||||
                          },
 | 
			
		||||
                          onClick: () => setShowConfirmResetModal(true),
 | 
			
		||||
                      },
 | 
			
		||||
                  ]
 | 
			
		||||
                : []),
 | 
			
		||||
        ],
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
            <ButtonWrapper>
 | 
			
		||||
                <Dropdown
 | 
			
		||||
                    menu={menu}
 | 
			
		||||
                    trigger={['click']}
 | 
			
		||||
                    dropdownRender={(menuNode) => <DropdownContainer>{menuNode}</DropdownContainer>}
 | 
			
		||||
                >
 | 
			
		||||
                    <Button icon={{ icon: 'Gear', color: 'gray', source: 'phosphor', size: '4xl' }} variant="text" />
 | 
			
		||||
                </Dropdown>
 | 
			
		||||
            </ButtonWrapper>
 | 
			
		||||
            <ConfirmationModal
 | 
			
		||||
                isOpen={!!showConfirmResetModal}
 | 
			
		||||
                handleConfirm={handleResetToDefault}
 | 
			
		||||
                handleClose={() => setShowConfirmResetModal(false)}
 | 
			
		||||
                modalTitle="Confirm reset to default template"
 | 
			
		||||
                modalText="Are you sure you want to reset your homepage to the organization's default template? You will lose all your personal modules."
 | 
			
		||||
                closeButtonText="Cancel"
 | 
			
		||||
                confirmButtonText="Confirm"
 | 
			
		||||
            />
 | 
			
		||||
        </>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
@ -54,7 +54,7 @@ export const ConfirmationModal = ({
 | 
			
		||||
            centered
 | 
			
		||||
            footer={
 | 
			
		||||
                <ButtonsContainer>
 | 
			
		||||
                    <Button variant="text" onClick={handleClose} data-testid="modal-cancel-button">
 | 
			
		||||
                    <Button variant="text" color="gray" onClick={handleClose} data-testid="modal-cancel-button">
 | 
			
		||||
                        {closeButtonText || 'Cancel'}
 | 
			
		||||
                    </Button>
 | 
			
		||||
                    <Button variant="filled" onClick={handleConfirm} data-testid="modal-confirm-button">
 | 
			
		||||
 | 
			
		||||
@ -69,6 +69,7 @@ query getMe {
 | 
			
		||||
            viewStructuredPropertiesPage
 | 
			
		||||
            manageApplications
 | 
			
		||||
            manageFeatures
 | 
			
		||||
            manageHomePageTemplates
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user