Skip to content
Open
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
Expand Up @@ -3,117 +3,136 @@ import { Stack, Text, Checkbox, Table, Flex, Box } from '@contentful/f36-compone
import type { ContentLifecyclePermissions } from '../types/config';
import type { ContentLifecycleEntityKey, EntityActionKey } from '../types/config';
import { ENTITY_AVAILABLE_ACTIONS, STANDARD_ACTIONS, ALL_ENTITIES } from '../types/config';
import { isActionAvailable } from '../../utils/permissions';
import { isActionAvailable, areAllAvailablePermissionsChecked } from '../../utils/permissions';

interface ContentLifecyclePermissionsTableProps {
permissions: ContentLifecyclePermissions;
onSelectAllToggle: () => void;
visibleEntities?: ContentLifecycleEntityKey[];
visibleActions?: EntityActionKey[];
onSelectAllToggle: (entities: ContentLifecycleEntityKey[]) => void;
onEntityActionToggle: (entity: ContentLifecycleEntityKey, action: EntityActionKey) => void;
onColumnToggle: (action: EntityActionKey) => void;
onColumnToggle: (entities: ContentLifecycleEntityKey[], action: EntityActionKey) => void;
onRowToggle: (entity: ContentLifecycleEntityKey) => void;
}

export const ContentLifecyclePermissionsTable: FC<ContentLifecyclePermissionsTableProps> = ({
permissions,
visibleEntities = ALL_ENTITIES,
visibleActions = STANDARD_ACTIONS,
onSelectAllToggle,
onEntityActionToggle,
onColumnToggle,
onRowToggle,
}) => (
<Stack flexDirection="column" spacing="spacing2Xs" alignItems="flex-start">
<Text>
Allow the MCP server to read, edit, create, delete, publish, un-publish, archive, unarchive,
or invoke entities within Contentful.
</Text>
<Box marginTop="spacingS">
<Checkbox isChecked={permissions.selectAll} onChange={onSelectAllToggle}>
Select all entities and actions in the table below
</Checkbox>
</Box>
}) => {
// Select-all is derived per-table from this table's own entities, so
// independent sections (classic vs. ExO) each track their own state.
const isSelectAllChecked = visibleEntities.every((entity) =>
areAllAvailablePermissionsChecked(entity, permissions[entity])
);

<div style={{ width: '100%', overflowX: 'auto' }}>
<Table style={{ minWidth: '800px' }}>
<Table.Head>
<Table.Row>
<Table.Cell></Table.Cell>
{STANDARD_ACTIONS.map((action) => {
// Get all entities that support this action
const entitiesWithAction = ALL_ENTITIES.filter((entity) =>
isActionAvailable(entity, action)
);
// Column is checked if all entities that support this action have it enabled
const isColumnChecked = entitiesWithAction.every(
(entity) => permissions[entity][action as keyof typeof permissions.entries]
return (
<Stack flexDirection="column" spacing="spacing2Xs" alignItems="flex-start">
<Text>
Allow the MCP server to read, edit, create, delete, publish, un-publish, archive, unarchive,
or invoke entities within Contentful.
</Text>
<Box marginTop="spacingS">
<Checkbox
isChecked={isSelectAllChecked}
onChange={() => onSelectAllToggle(visibleEntities)}>
Select all entities and actions in the table below
</Checkbox>
</Box>

<div style={{ width: '100%', overflowX: 'auto' }}>
<Table style={{ minWidth: '800px' }}>
<Table.Head>
<Table.Row>
<Table.Cell></Table.Cell>
{visibleActions.map((action) => {
// Get all VISIBLE entities that support this action
const entitiesWithAction = visibleEntities.filter((entity) =>
isActionAvailable(entity, action)
);
// Column is checked if all entities that support this action have it enabled
const isColumnChecked = entitiesWithAction.every(
(entity) => permissions[entity][action as keyof typeof permissions.entries]
);

return (
<Table.Cell key={action} style={{ textAlign: 'center', verticalAlign: 'bottom' }}>
<Flex flexDirection="column" alignItems="center" gap="spacingXs">
<Text fontWeight="fontWeightMedium" fontSize="fontSizeS">
{action.charAt(0).toUpperCase() + action.slice(1)}
</Text>
<Checkbox
isChecked={isColumnChecked}
onChange={() => onColumnToggle(visibleEntities, action as EntityActionKey)}
/>
</Flex>
</Table.Cell>
);
})}
</Table.Row>
</Table.Head>
<Table.Body>
{visibleEntities.map((entity) => {
const displayNames: Record<ContentLifecycleEntityKey, string> = {
entries: 'Entries',
assets: 'Assets',
contentTypes: 'Content types',
aiActions: 'AI actions',
editorInterfaces: 'Editor interfaces',
environments: 'Environments',
locales: 'Locales',
orgs: 'Organizations',
spaces: 'Spaces',
tags: 'Tags',
concepts: 'Concepts',
conceptSchemes: 'Concept schemes',
componentTypes: 'Component types',
experiences: 'Experiences',
templates: 'Templates',
dataAssemblies: 'Data assemblies',
fragments: 'Fragments',
};

// Get available actions for this entity
const availableActions = ENTITY_AVAILABLE_ACTIONS[entity];
// Row is checked if all available actions for this entity are enabled
const isRowChecked = availableActions.every(
(action) => permissions[entity][action as keyof typeof permissions.entries]
);

return (
<Table.Cell key={action} style={{ textAlign: 'center', verticalAlign: 'bottom' }}>
<Flex flexDirection="column" alignItems="center" gap="spacingXs">
<Text fontWeight="fontWeightMedium" fontSize="fontSizeS">
{action.charAt(0).toUpperCase() + action.slice(1)}
</Text>
<Checkbox
isChecked={isColumnChecked}
onChange={() => onColumnToggle(action as EntityActionKey)}
/>
</Flex>
</Table.Cell>
<Table.Row key={entity}>
<Table.Cell>
<Checkbox isChecked={isRowChecked} onChange={() => onRowToggle(entity)}>
{displayNames[entity]}
</Checkbox>
</Table.Cell>
{visibleActions.map((action) => {
const isAvailable = isActionAvailable(entity, action);
return (
<Table.Cell key={action}>
<Flex justifyContent="center">
<Checkbox
isDisabled={!isAvailable}
isChecked={
permissions[entity][action as keyof typeof permissions.entries]
}
onChange={() => onEntityActionToggle(entity, action as EntityActionKey)}
/>
</Flex>
</Table.Cell>
);
})}
</Table.Row>
);
})}
</Table.Row>
</Table.Head>
<Table.Body>
{ALL_ENTITIES.map((entity) => {
const displayNames: Record<ContentLifecycleEntityKey, string> = {
entries: 'Entries',
assets: 'Assets',
contentTypes: 'Content types',
aiActions: 'AI actions',
editorInterfaces: 'Editor interfaces',
environments: 'Environments',
locales: 'Locales',
orgs: 'Organizations',
spaces: 'Spaces',
tags: 'Tags',
concepts: 'Concepts',
conceptSchemes: 'Concept schemes',
};

// Get available actions for this entity
const availableActions = ENTITY_AVAILABLE_ACTIONS[entity];
// Row is checked if all available actions for this entity are enabled
const isRowChecked = availableActions.every(
(action) => permissions[entity][action as keyof typeof permissions.entries]
);

return (
<Table.Row key={entity}>
<Table.Cell>
<Checkbox isChecked={isRowChecked} onChange={() => onRowToggle(entity)}>
{displayNames[entity]}
</Checkbox>
</Table.Cell>
{STANDARD_ACTIONS.map((action) => {
const isAvailable = isActionAvailable(entity, action);
return (
<Table.Cell key={action}>
<Flex justifyContent="center">
<Checkbox
isDisabled={!isAvailable}
isChecked={
permissions[entity][action as keyof typeof permissions.entries]
}
onChange={() => onEntityActionToggle(entity, action as EntityActionKey)}
/>
</Flex>
</Table.Cell>
);
})}
</Table.Row>
);
})}
</Table.Body>
</Table>
</div>
</Stack>
);
</Table.Body>
</Table>
</div>
</Stack>
);
};

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,48 +1,42 @@
import { type FC } from 'react';
import { Stack, Heading, Text, Accordion } from '@contentful/f36-components';
import { Stack, Heading, Text, Accordion, Note } from '@contentful/f36-components';
import { ContentLifecyclePermissionsTable } from './ContentLifecyclePermissionsTable';
import { OtherFeaturesPermissions } from './OtherFeaturesPermissions';
import { MigrationPermissions } from './MigrationPermissions';
import { CLASSIC_ENTITIES, EXO_ENTITIES, EXO_ACTIONS } from '../types/config';
import type {
ContentLifecyclePermissions,
OtherFeaturesPermissions as OtherFeaturesPermissionsType,
MigrationPermissions as MigrationPermissionsType,
OtherFeaturesPermissionKey,
MigrationPermissionKey,
ContentLifecycleEntityKey,
EntityActionKey,
} from '../types/config';

interface PermissionsSectionProps {
contentLifecyclePermissions: ContentLifecyclePermissions;
otherFeaturesPermissions: OtherFeaturesPermissionsType;
migrationPermissions: MigrationPermissionsType;
expandedAccordions: {
contentLifecycle: boolean;
experienceOrchestration: boolean;
otherFeatures: boolean;
migration: boolean;
};
onAccordionToggle: (section: string, expanded: boolean) => void;
onSelectAllToggle: () => void;
onSelectAllToggle: (entities: ContentLifecycleEntityKey[]) => void;
onEntityActionToggle: (entity: ContentLifecycleEntityKey, action: EntityActionKey) => void;
onColumnToggle: (action: EntityActionKey) => void;
onColumnToggle: (entities: ContentLifecycleEntityKey[], action: EntityActionKey) => void;
onRowToggle: (entity: ContentLifecycleEntityKey) => void;
onOtherFeatureToggle: (permission: OtherFeaturesPermissionKey) => void;
onMigrationToggle: (permission: MigrationPermissionKey) => void;
}

export const PermissionsSection: FC<PermissionsSectionProps> = ({
contentLifecyclePermissions,
otherFeaturesPermissions,
migrationPermissions,
expandedAccordions,
onAccordionToggle,
onSelectAllToggle,
onEntityActionToggle,
onColumnToggle,
onRowToggle,
onOtherFeatureToggle,
onMigrationToggle,
}) => (
<Stack
flexDirection="column"
Expand Down Expand Up @@ -70,6 +64,7 @@ export const PermissionsSection: FC<PermissionsSectionProps> = ({
}}>
<ContentLifecyclePermissionsTable
permissions={contentLifecyclePermissions}
visibleEntities={CLASSIC_ENTITIES}
onSelectAllToggle={onSelectAllToggle}
onEntityActionToggle={onEntityActionToggle}
onColumnToggle={onColumnToggle}
Expand All @@ -79,37 +74,47 @@ export const PermissionsSection: FC<PermissionsSectionProps> = ({
</Accordion.Item>

<Accordion.Item
title="Actions on other features"
isExpanded={expandedAccordions.otherFeatures}
onExpand={() => onAccordionToggle('otherFeatures', true)}
onCollapse={() => onAccordionToggle('otherFeatures', false)}>
title="Experience orchestration actions"
isExpanded={expandedAccordions.experienceOrchestration}
onExpand={() => onAccordionToggle('experienceOrchestration', true)}
onCollapse={() => onAccordionToggle('experienceOrchestration', false)}>
<div
style={{
marginTop: '-12px',
boxSizing: 'border-box',
paddingRight: '16px',
}}>
<OtherFeaturesPermissions
permissions={otherFeaturesPermissions}
onPermissionToggle={onOtherFeatureToggle}
<Note variant="neutral" style={{ marginTop: '12px', marginBottom: '16px' }}>
These tools are only available in Experience Orchestration (ExO) compatible spaces. If
your space is not ExO compatible, enabling these permissions has no effect and the
corresponding tools will not be available.
</Note>
<ContentLifecyclePermissionsTable
permissions={contentLifecyclePermissions}
visibleEntities={EXO_ENTITIES}
visibleActions={EXO_ACTIONS}
onSelectAllToggle={onSelectAllToggle}
onEntityActionToggle={onEntityActionToggle}
onColumnToggle={onColumnToggle}
onRowToggle={onRowToggle}
/>
</div>
</Accordion.Item>

<Accordion.Item
title="Migration permissions"
isExpanded={expandedAccordions.migration}
onExpand={() => onAccordionToggle('migration', true)}
onCollapse={() => onAccordionToggle('migration', false)}>
title="Actions on other features"
isExpanded={expandedAccordions.otherFeatures}
onExpand={() => onAccordionToggle('otherFeatures', true)}
onCollapse={() => onAccordionToggle('otherFeatures', false)}>
<div
style={{
marginTop: '-12px',
boxSizing: 'border-box',
paddingRight: '16px',
}}>
<MigrationPermissions
permissions={migrationPermissions}
onPermissionToggle={onMigrationToggle}
<OtherFeaturesPermissions
permissions={otherFeaturesPermissions}
onPermissionToggle={onOtherFeatureToggle}
/>
</div>
</Accordion.Item>
Expand Down
Loading
Loading