🚩(frontend) add Analytics feature flag on Duplicate with Children

We want to have a fine grained control over the
Duplicate with Children feature.
By adding the feature flag for Duplicate with Children,
we can enable or disable this feature for specific
users or groups without deploying new code.
This allows us to test the feature in a controlled
environment and gather feedback before a full rollout.
This commit is contained in:
Anthony LC
2026-09-25 14:06:22 +02:00
parent 420d56e3ce
commit f0d718fa3e
3 changed files with 166 additions and 1 deletions
+1
View File
@@ -8,6 +8,7 @@ and this project adheres to
### Added
- 🚩(setting) add feature flag on Duplicate with Children #2721
- 💄(frontend) redesign 404 error standalone page #2696
- 💄(frontend) redesign 403 access denied page #2720
- ✨(frontend) duplicate with subdocuments #2584
@@ -39,6 +39,7 @@ import SharedIcon from '@/icons/shared.svg';
import StarSlashIcon from '@/icons/star-slash.svg';
import StarIcon from '@/icons/star.svg';
import DeleteIcon from '@/icons/trash.svg';
import { useAnalytics } from '@/libs/Analytics';
import { useFocusStore, useResponsiveStore } from '@/stores';
import { isMacOS } from '@/utils/userAgent';
@@ -52,6 +53,8 @@ import {
import { useCopyDocLink, useTrans } from '../hooks';
import { Doc, Role } from '../types';
const DUPLICATE_WITH_CHILDREN_FEATURE_FLAG = 'duplicate_with_children';
const ConfirmationDuplicateModal = dynamic(
() =>
import('@/docs/doc-management/components/ConfirmationDuplicateModal').then(
@@ -154,6 +157,10 @@ const DocToolBoxComponent = ({
const [isModalLeaveOpen, setIsModalLeaveOpen] = useState(false);
const [isModalMoveOpen, setIsModalMoveOpen] = useState(false);
const { onClick: onButtonClick, ...buttonPropsLeft } = buttonProps || {};
const { isFeatureFlagActivated } = useAnalytics();
const duplicateWithChildrenAllowed = !!(
isFeatureFlagActivated(DUPLICATE_WITH_CHILDREN_FEATURE_FLAG) && doc.numchild
);
const editor = useEditorStore((state) => state.editor);
const wordCountLabel = useMemo(() => {
@@ -301,7 +308,7 @@ const DocToolBoxComponent = ({
icon: <ContentCopyIcon width={18} height={18} aria-hidden="true" />,
isDisabled: !doc.abilities.duplicate,
callback: () => {
if (doc.numchild) {
if (duplicateWithChildrenAllowed) {
setIsModalDuplicateOpen(true);
} else {
duplicateDoc({
@@ -0,0 +1,157 @@
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { describe, expect, test, vi } from 'vitest';
import { AppWrapper } from '@/tests/utils';
import { Doc, LinkReach, Role } from '../../types';
import { DocToolBox } from '../DocToolBox';
vi.mock('next/router', () => ({
useRouter: () => ({
push: vi.fn(),
pathname: '/',
}),
}));
vi.mock('@/features/auth', () => ({
useAuth: () => ({ authenticated: true }),
}));
const isFeatureFlagActivatedMock = vi.fn();
vi.mock('@/libs/Analytics', () => ({
useAnalytics: () => ({
isFeatureFlagActivated: isFeatureFlagActivatedMock,
trackEvent: vi.fn(),
}),
}));
const duplicateDocMock = vi.fn();
vi.mock('@/docs/doc-management/components/ConfirmationDuplicateModal', () => ({
useDuplicatedDoc: () => ({
mutate: duplicateDocMock,
isPending: false,
}),
ConfirmationDuplicateModal: () => (
<div data-testid="confirmation-duplicate-modal" />
),
}));
const createDoc = (overrides: Partial<Doc> = {}): Doc => ({
id: 'doc-id',
title: 'My document',
created_at: '',
creator: '',
deleted_at: null,
depth: 1,
path: '0001',
is_favorite: false,
link_reach: LinkReach.RESTRICTED,
computed_link_reach: LinkReach.RESTRICTED,
ancestors_link_reach: LinkReach.RESTRICTED,
nb_accesses_direct: 0,
nb_accesses_ancestors: 0,
numchild: 0,
updated_at: '',
user_role: Role.OWNER,
abilities: {
accesses_manage: false,
accesses_view: false,
ai_proxy: false,
ai_transform: false,
ai_translate: false,
attachment_upload: false,
children_create: false,
children_list: false,
collaboration_auth: false,
comment: false,
content_patch: false,
content_retrieve: false,
destroy: false,
duplicate: true,
favorite: false,
formatted_content: false,
invite_owner: false,
leave: false,
link_configuration: false,
link_select_options: {},
media_auth: false,
move: false,
partial_update: true,
restore: false,
retrieve: false,
search: false,
update: false,
versions_list: false,
},
...overrides,
});
const openDuplicateOption = async () => {
const trigger = screen.getByRole('button', {
name: /Open the document options/i,
});
await userEvent.click(trigger);
const duplicateOption = await screen.findByRole('menuitem', {
name: 'Duplicate',
});
await userEvent.click(duplicateOption);
};
describe('<DocToolBox /> - duplicate with children', () => {
test('opens the confirmation modal when the feature flag is active and the doc has children', async () => {
isFeatureFlagActivatedMock.mockReturnValue(true);
const doc = createDoc({ numchild: 3 });
render(<DocToolBox doc={doc} isCurrentDoc />, { wrapper: AppWrapper });
await openDuplicateOption();
await waitFor(() => {
expect(
screen.getByTestId('confirmation-duplicate-modal'),
).toBeInTheDocument();
});
expect(duplicateDocMock).not.toHaveBeenCalled();
});
test('duplicates directly when the feature flag is inactive, even if the doc has children', async () => {
isFeatureFlagActivatedMock.mockReturnValue(false);
const doc = createDoc({ numchild: 3 });
render(<DocToolBox doc={doc} isCurrentDoc />, { wrapper: AppWrapper });
await openDuplicateOption();
await waitFor(() => {
expect(duplicateDocMock).toHaveBeenCalledWith({
docId: doc.id,
canSave: doc.abilities.partial_update,
});
});
expect(
screen.queryByTestId('confirmation-duplicate-modal'),
).not.toBeInTheDocument();
});
test('duplicates directly when the feature flag is active but the doc has no children', async () => {
isFeatureFlagActivatedMock.mockReturnValue(true);
const doc = createDoc({ numchild: 0 });
render(<DocToolBox doc={doc} isCurrentDoc />, { wrapper: AppWrapper });
await openDuplicateOption();
await waitFor(() => {
expect(duplicateDocMock).toHaveBeenCalledWith({
docId: doc.id,
canSave: doc.abilities.partial_update,
});
});
expect(
screen.queryByTestId('confirmation-duplicate-modal'),
).not.toBeInTheDocument();
});
});