From ea9efabe2afba3ecd467dbc05c70d9b4b91c4019 Mon Sep 17 00:00:00 2001 From: David Shibley Date: Wed, 15 Jul 2026 10:51:19 -0600 Subject: [PATCH 1/4] feat(drive-integration): display validationFindings in mapping-review UI with block/warn badges [INTEG-4383] Consumes validationFindings from the PENDING_REVIEW suspend payload and surfaces them in the mapping-review overview panel. Block findings show a "Needs attention" badge per entry and gate the Create button behind an explicit acknowledgement checkbox; warn findings show a "Warning" badge without blocking. Co-Authored-By: Claude Sonnet 4.6 --- .../components/overview/OverviewEntryList.tsx | 21 +++++ .../components/overview/OverviewSection.tsx | 40 ++++++++- .../Page/components/review/ReviewPage.tsx | 9 +- apps/drive-integration/src/types/workflow.ts | 19 +++++ .../overview/OverviewEntryList.spec.tsx | 85 +++++++++++++++++++ .../components/review/ReviewPage.spec.tsx | 56 ++++++++++++ 6 files changed, 227 insertions(+), 3 deletions(-) create mode 100644 apps/drive-integration/test/locations/Page/components/overview/OverviewEntryList.spec.tsx diff --git a/apps/drive-integration/src/locations/Page/components/overview/OverviewEntryList.tsx b/apps/drive-integration/src/locations/Page/components/overview/OverviewEntryList.tsx index 5f0dd0c2b9..b9c8253d09 100644 --- a/apps/drive-integration/src/locations/Page/components/overview/OverviewEntryList.tsx +++ b/apps/drive-integration/src/locations/Page/components/overview/OverviewEntryList.tsx @@ -2,6 +2,7 @@ import { Badge, Box, Card, Checkbox, Flex, Paragraph, Text } from '@contentful/f import tokens from '@contentful/f36-tokens'; import { cx } from '@emotion/css'; import type { EntryListRow as OverviewEntryListRow } from '../../../../utils/overviewEntryList'; +import type { ValidationFinding } from '@types'; import { noMappedContentBadge, treeChildRowBase, @@ -18,6 +19,8 @@ export interface OverviewEntryListProps { onSelect: (entryIndex: number) => void; onToggleEntrySelection: (entryKey: string, isSelected: boolean) => void; areEntrySelectionsDisabled?: boolean; + /** Validation findings keyed by entry index, for badge rendering. */ + findingsByEntryIndex?: ReadonlyMap; } interface OverviewEntryRowCardProps { @@ -29,6 +32,7 @@ interface OverviewEntryRowCardProps { areEntrySelectionsDisabled: boolean; showTreeLines: boolean; isLastRow?: boolean; + findingsByEntryIndex?: ReadonlyMap; } function OverviewEntryRowCard({ @@ -40,9 +44,13 @@ function OverviewEntryRowCard({ areEntrySelectionsDisabled, showTreeLines, isLastRow = true, + findingsByEntryIndex, }: OverviewEntryRowCardProps) { const isSelected = row.entryIndex === selectedEntryIndex; const isEntrySelectedForCreation = selectedEntryKeys.has(row.id); + const entryFindings = findingsByEntryIndex?.get(row.entryIndex) ?? []; + const hasBlockFindings = entryFindings.some((f) => f.severity === 'block'); + const hasWarnFindings = entryFindings.some((f) => f.severity === 'warn'); const treeLineClass = showTreeLines && cx(treeChildRowBase, isLastRow ? treeChildRowLast : treeChildRowNotLast); @@ -95,6 +103,16 @@ function OverviewEntryRowCard({ No mapped content )} + {hasBlockFindings && ( + + Needs attention + + )} + {!hasBlockFindings && hasWarnFindings && ( + + Warning + + )} @@ -112,6 +130,7 @@ function OverviewEntryRowCard({ areEntrySelectionsDisabled={areEntrySelectionsDisabled} showTreeLines isLastRow={index === row.children.length - 1} + findingsByEntryIndex={findingsByEntryIndex} /> ))} @@ -137,6 +156,7 @@ export function OverviewEntryList({ onSelect, onToggleEntrySelection, areEntrySelectionsDisabled = false, + findingsByEntryIndex, }: OverviewEntryListProps) { return ( @@ -150,6 +170,7 @@ export function OverviewEntryList({ onToggleEntrySelection={onToggleEntrySelection} areEntrySelectionsDisabled={areEntrySelectionsDisabled} showTreeLines={false} + findingsByEntryIndex={findingsByEntryIndex} /> ))} diff --git a/apps/drive-integration/src/locations/Page/components/overview/OverviewSection.tsx b/apps/drive-integration/src/locations/Page/components/overview/OverviewSection.tsx index 7b6d50f7b4..d9df977942 100644 --- a/apps/drive-integration/src/locations/Page/components/overview/OverviewSection.tsx +++ b/apps/drive-integration/src/locations/Page/components/overview/OverviewSection.tsx @@ -1,7 +1,7 @@ import { useMemo } from 'react'; -import { Box, Button, Flex, Note, Paragraph, Text } from '@contentful/f36-components'; +import { Box, Button, Checkbox, Flex, Note, Paragraph, Text } from '@contentful/f36-components'; import { LightbulbIcon } from '@contentful/f36-icons'; -import type { MappingReviewSuspendPayload } from '@types'; +import type { MappingReviewSuspendPayload, ValidationFinding } from '@types'; import { buildEntryListFromEntryBlockGraph } from '../../../../utils/overviewEntryList'; import { OverviewEntryList } from './OverviewEntryList'; import { overviewSectionBox, overviewSectionBoxScrollable } from './OverviewSection.styles'; @@ -18,6 +18,10 @@ interface OverviewProps { isCtaLoading?: boolean; isCtaDisabled?: boolean; areEntrySelectionsDisabled?: boolean; + /** Called with `true` when the user checks the block-findings acknowledgement. */ + onBlockFindingsAcknowledged?: (acknowledged: boolean) => void; + /** Whether the user has acknowledged block findings. */ + blockFindingsAcknowledged?: boolean; } const OverviewSection = ({ @@ -31,6 +35,8 @@ const OverviewSection = ({ isCtaLoading = false, isCtaDisabled = false, areEntrySelectionsDisabled = false, + onBlockFindingsAcknowledged, + blockFindingsAcknowledged = false, }: OverviewProps) => { const entryRows = useMemo( () => @@ -42,6 +48,19 @@ const OverviewSection = ({ [payload.entryBlockGraph.entries, payload.contentTypes, payload.referenceGraph.edges] ); + const findingsByEntryIndex = useMemo((): ReadonlyMap => { + const map = new Map(); + for (const finding of payload.validationFindings ?? []) { + if (finding.entryIndex === undefined) continue; + const list = map.get(finding.entryIndex) ?? []; + list.push(finding); + map.set(finding.entryIndex, list); + } + return map; + }, [payload.validationFindings]); + + const hasBlockFindings = (payload.validationFindings ?? []).some((f) => f.severity === 'block'); + return ( <> @@ -59,6 +78,22 @@ const OverviewSection = ({ + {hasBlockFindings && ( + + + + Some entries have issues that may prevent the content from being created correctly. + Review the highlighted entries before proceeding. + + onBlockFindingsAcknowledged?.(event.target.checked)}> + I have reviewed the issues and want to proceed + + + + )} + @@ -91,6 +126,7 @@ const OverviewSection = ({ onSelect={onSelectEntryIndex} onToggleEntrySelection={onToggleEntrySelection} areEntrySelectionsDisabled={areEntrySelectionsDisabled} + findingsByEntryIndex={findingsByEntryIndex} /> )} diff --git a/apps/drive-integration/src/locations/Page/components/review/ReviewPage.tsx b/apps/drive-integration/src/locations/Page/components/review/ReviewPage.tsx index 3ffdca3a48..1d8dc50e10 100644 --- a/apps/drive-integration/src/locations/Page/components/review/ReviewPage.tsx +++ b/apps/drive-integration/src/locations/Page/components/review/ReviewPage.tsx @@ -52,6 +52,7 @@ export const ReviewPage = ({ const [createdEntries, setCreatedEntries] = useState(null); const [isSummaryModalOpen, setIsSummaryModalOpen] = useState(false); const [createError, setCreateError] = useState(null); + const [blockFindingsAcknowledged, setBlockFindingsAcknowledged] = useState(false); const [entryBlockGraph, setEntryBlockGraph] = useState(() => structuredClone(payload.entryBlockGraph) ); @@ -65,6 +66,7 @@ export const ReviewPage = ({ const nextEntryBlockGraph = structuredClone(payload.entryBlockGraph); setEntryBlockGraph(nextEntryBlockGraph); setSelectedEntryKeys(getAllEntrySelectionKeys(nextEntryBlockGraph.entries)); + setBlockFindingsAcknowledged(false); // eslint-disable-next-line react-hooks/exhaustive-deps -- only re-init on run identity }, [runId, payload.documentId]); @@ -84,6 +86,7 @@ export const ReviewPage = ({ }, [payload.contentTypes]); const hasCreatedEntries = createdEntries !== null; const isMappingDisabled = isCreatePending || hasCreatedEntries; + const hasBlockFindings = (payload.validationFindings ?? []).some((f) => f.severity === 'block'); const selectedEntryCount = useMemo( () => countSelectedEntries(entryBlockGraph.entries, selectedEntryKeys), [entryBlockGraph.entries, selectedEntryKeys] @@ -263,8 +266,12 @@ export const ReviewPage = ({ ctaLabel={hasCreatedEntries ? 'View entries' : 'Create selected entries'} onCtaClick={handleCreateOrViewEntries} isCtaLoading={isCreatePending} - isCtaDisabled={!hasCreatedEntries && !hasSelectedEntries} + isCtaDisabled={ + !hasCreatedEntries && (!hasSelectedEntries || (hasBlockFindings && !blockFindingsAcknowledged)) + } areEntrySelectionsDisabled={isMappingDisabled} + blockFindingsAcknowledged={blockFindingsAcknowledged} + onBlockFindingsAcknowledged={setBlockFindingsAcknowledged} /> ({ + id: `row-${entryIndex}`, + entryIndex, + contentTypeName: 'Article', + entryTitle: label, + children: [], +}); + +const renderList = ( + rows: EntryListRow[], + findingsByEntryIndex?: ReadonlyMap +) => + render( + + ); + +afterEach(() => cleanup()); + +describe('OverviewEntryList — validation finding badges (INTEG-4383)', () => { + it('renders a "Needs attention" badge for entries with block findings', () => { + const rows = [makeRow(0, 'Entry A')]; + const findings: ValidationFinding[] = [ + { code: 'required-field-missing', message: 'title missing', severity: 'block', entryIndex: 0 }, + ]; + renderList(rows, new Map([[0, findings]])); + + expect(screen.getByText('Needs attention')).toBeTruthy(); + expect(screen.queryByText('Warning')).toBeNull(); + }); + + it('renders a "Warning" badge for entries with only warn findings', () => { + const rows = [makeRow(0, 'Entry A')]; + const findings: ValidationFinding[] = [ + { code: 'displayField-blank', message: 'title blank', severity: 'warn', entryIndex: 0 }, + ]; + renderList(rows, new Map([[0, findings]])); + + expect(screen.getByText('Warning')).toBeTruthy(); + expect(screen.queryByText('Needs attention')).toBeNull(); + }); + + it('renders "Needs attention" (not Warning) when entry has both block and warn findings', () => { + const rows = [makeRow(0, 'Entry A')]; + const findings: ValidationFinding[] = [ + { code: 'required-field-missing', message: 'title missing', severity: 'block', entryIndex: 0 }, + { code: 'displayField-blank', message: 'title blank', severity: 'warn', entryIndex: 0 }, + ]; + renderList(rows, new Map([[0, findings]])); + + expect(screen.getByText('Needs attention')).toBeTruthy(); + expect(screen.queryByText('Warning')).toBeNull(); + }); + + it('renders no finding badges when findingsByEntryIndex is undefined', () => { + const rows = [makeRow(0, 'Entry A')]; + renderList(rows, undefined); + + expect(screen.queryByText('Needs attention')).toBeNull(); + expect(screen.queryByText('Warning')).toBeNull(); + }); + + it('renders no finding badges for entries with no findings', () => { + const rows = [makeRow(0, 'Entry A'), makeRow(1, 'Entry B')]; + const findings: ValidationFinding[] = [ + { code: 'required-field-missing', message: 'title missing', severity: 'block', entryIndex: 1 }, + ]; + renderList(rows, new Map([[1, findings]])); + + // Only entry 1 should have the badge + expect(screen.getAllByText('Needs attention')).toHaveLength(1); + }); +}); diff --git a/apps/drive-integration/test/locations/Page/components/review/ReviewPage.spec.tsx b/apps/drive-integration/test/locations/Page/components/review/ReviewPage.spec.tsx index f40ed2295f..2f0d9b402d 100644 --- a/apps/drive-integration/test/locations/Page/components/review/ReviewPage.spec.tsx +++ b/apps/drive-integration/test/locations/Page/components/review/ReviewPage.spec.tsx @@ -125,6 +125,62 @@ const renderReviewPage = (payload: MappingReviewSuspendPayload = createPayload() ); +describe('ReviewPage — block findings acknowledgement (INTEG-4383)', () => { + beforeEach(() => { + sdk = createMockSDK() as PageAppSDK; + vi.clearAllMocks(); + }); + + it('disables Create button when block findings are present and not acknowledged', () => { + const payload: MappingReviewSuspendPayload = { + ...createPayload(), + validationFindings: [ + { code: 'required-field-missing', message: 'title missing', severity: 'block', entryIndex: 0 }, + ], + }; + renderReviewPage(payload); + + expect(screen.getByRole('button', { name: 'Create selected entries' })).toBeDisabled(); + }); + + it('enables Create button after user acknowledges block findings', () => { + const payload: MappingReviewSuspendPayload = { + ...createPayload(), + validationFindings: [ + { code: 'required-field-missing', message: 'title missing', severity: 'block', entryIndex: 0 }, + ], + }; + renderReviewPage(payload); + + const checkbox = screen.getByRole('checkbox', { + name: 'I have reviewed the issues and want to proceed', + }); + fireEvent.click(checkbox); + + expect(screen.getByRole('button', { name: 'Create selected entries' })).toBeEnabled(); + }); + + it('does not disable Create button when only warn findings are present', () => { + const payload: MappingReviewSuspendPayload = { + ...createPayload(), + validationFindings: [ + { code: 'displayField-blank', message: 'title blank', severity: 'warn', entryIndex: 0 }, + ], + }; + renderReviewPage(payload); + + expect(screen.getByRole('button', { name: 'Create selected entries' })).toBeEnabled(); + }); + + it('does not show acknowledgement note when there are no block findings', () => { + renderReviewPage(); + + expect( + screen.queryByText('I have reviewed the issues and want to proceed') + ).toBeNull(); + }); +}); + describe('ReviewPage entry selection', () => { beforeEach(() => { sdk = createMockSDK() as PageAppSDK; From 733fe2590a34fca2f7f4ba619c8d598336ca2d20 Mon Sep 17 00:00:00 2001 From: David Shibley Date: Wed, 15 Jul 2026 11:30:35 -0600 Subject: [PATCH 2/4] style(drive-integration): apply prettier formatting [INTEG-4383] Co-Authored-By: Claude Sonnet 4.6 --- .../components/overview/OverviewSection.tsx | 4 ++-- .../Page/components/review/ReviewPage.tsx | 3 ++- .../overview/OverviewEntryList.spec.tsx | 21 ++++++++++++++++--- .../components/review/ReviewPage.spec.tsx | 18 +++++++++++----- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/apps/drive-integration/src/locations/Page/components/overview/OverviewSection.tsx b/apps/drive-integration/src/locations/Page/components/overview/OverviewSection.tsx index d9df977942..8593474cee 100644 --- a/apps/drive-integration/src/locations/Page/components/overview/OverviewSection.tsx +++ b/apps/drive-integration/src/locations/Page/components/overview/OverviewSection.tsx @@ -82,8 +82,8 @@ const OverviewSection = ({ - Some entries have issues that may prevent the content from being created correctly. - Review the highlighted entries before proceeding. + Some entries have issues that may prevent the content from being created + correctly. Review the highlighted entries before proceeding. { it('renders a "Needs attention" badge for entries with block findings', () => { const rows = [makeRow(0, 'Entry A')]; const findings: ValidationFinding[] = [ - { code: 'required-field-missing', message: 'title missing', severity: 'block', entryIndex: 0 }, + { + code: 'required-field-missing', + message: 'title missing', + severity: 'block', + entryIndex: 0, + }, ]; renderList(rows, new Map([[0, findings]])); @@ -55,7 +60,12 @@ describe('OverviewEntryList — validation finding badges (INTEG-4383)', () => { it('renders "Needs attention" (not Warning) when entry has both block and warn findings', () => { const rows = [makeRow(0, 'Entry A')]; const findings: ValidationFinding[] = [ - { code: 'required-field-missing', message: 'title missing', severity: 'block', entryIndex: 0 }, + { + code: 'required-field-missing', + message: 'title missing', + severity: 'block', + entryIndex: 0, + }, { code: 'displayField-blank', message: 'title blank', severity: 'warn', entryIndex: 0 }, ]; renderList(rows, new Map([[0, findings]])); @@ -75,7 +85,12 @@ describe('OverviewEntryList — validation finding badges (INTEG-4383)', () => { it('renders no finding badges for entries with no findings', () => { const rows = [makeRow(0, 'Entry A'), makeRow(1, 'Entry B')]; const findings: ValidationFinding[] = [ - { code: 'required-field-missing', message: 'title missing', severity: 'block', entryIndex: 1 }, + { + code: 'required-field-missing', + message: 'title missing', + severity: 'block', + entryIndex: 1, + }, ]; renderList(rows, new Map([[1, findings]])); diff --git a/apps/drive-integration/test/locations/Page/components/review/ReviewPage.spec.tsx b/apps/drive-integration/test/locations/Page/components/review/ReviewPage.spec.tsx index 2f0d9b402d..81cacf04de 100644 --- a/apps/drive-integration/test/locations/Page/components/review/ReviewPage.spec.tsx +++ b/apps/drive-integration/test/locations/Page/components/review/ReviewPage.spec.tsx @@ -135,7 +135,12 @@ describe('ReviewPage — block findings acknowledgement (INTEG-4383)', () => { const payload: MappingReviewSuspendPayload = { ...createPayload(), validationFindings: [ - { code: 'required-field-missing', message: 'title missing', severity: 'block', entryIndex: 0 }, + { + code: 'required-field-missing', + message: 'title missing', + severity: 'block', + entryIndex: 0, + }, ], }; renderReviewPage(payload); @@ -147,7 +152,12 @@ describe('ReviewPage — block findings acknowledgement (INTEG-4383)', () => { const payload: MappingReviewSuspendPayload = { ...createPayload(), validationFindings: [ - { code: 'required-field-missing', message: 'title missing', severity: 'block', entryIndex: 0 }, + { + code: 'required-field-missing', + message: 'title missing', + severity: 'block', + entryIndex: 0, + }, ], }; renderReviewPage(payload); @@ -175,9 +185,7 @@ describe('ReviewPage — block findings acknowledgement (INTEG-4383)', () => { it('does not show acknowledgement note when there are no block findings', () => { renderReviewPage(); - expect( - screen.queryByText('I have reviewed the issues and want to proceed') - ).toBeNull(); + expect(screen.queryByText('I have reviewed the issues and want to proceed')).toBeNull(); }); }); From 956958d10c316c37e1f335c90f7630a655d36c4f Mon Sep 17 00:00:00 2001 From: David Shibley Date: Wed, 15 Jul 2026 13:01:29 -0600 Subject: [PATCH 3/4] refactor(drive-integration): address PR review comments [INTEG-4383] - Convert ValidationFindingSeverity from type alias to enum - Remove JSDoc comment from validationFindings field - Pass hasBlockFindings from ReviewPage to OverviewSection to avoid computing it twice - Update all severity comparisons and test fixtures to use enum values Co-Authored-By: Claude Sonnet 4.6 --- .../Page/components/overview/OverviewEntryList.tsx | 5 +++-- .../Page/components/overview/OverviewSection.tsx | 7 ++++--- .../locations/Page/components/review/ReviewPage.tsx | 1 + apps/drive-integration/src/types/workflow.ts | 7 ++++--- .../components/overview/OverviewEntryList.spec.tsx | 11 ++++++----- .../Page/components/review/ReviewPage.spec.tsx | 8 ++++---- 6 files changed, 22 insertions(+), 17 deletions(-) diff --git a/apps/drive-integration/src/locations/Page/components/overview/OverviewEntryList.tsx b/apps/drive-integration/src/locations/Page/components/overview/OverviewEntryList.tsx index b9c8253d09..233a4f68c8 100644 --- a/apps/drive-integration/src/locations/Page/components/overview/OverviewEntryList.tsx +++ b/apps/drive-integration/src/locations/Page/components/overview/OverviewEntryList.tsx @@ -2,6 +2,7 @@ import { Badge, Box, Card, Checkbox, Flex, Paragraph, Text } from '@contentful/f import tokens from '@contentful/f36-tokens'; import { cx } from '@emotion/css'; import type { EntryListRow as OverviewEntryListRow } from '../../../../utils/overviewEntryList'; +import { ValidationFindingSeverity } from '@types'; import type { ValidationFinding } from '@types'; import { noMappedContentBadge, @@ -49,8 +50,8 @@ function OverviewEntryRowCard({ const isSelected = row.entryIndex === selectedEntryIndex; const isEntrySelectedForCreation = selectedEntryKeys.has(row.id); const entryFindings = findingsByEntryIndex?.get(row.entryIndex) ?? []; - const hasBlockFindings = entryFindings.some((f) => f.severity === 'block'); - const hasWarnFindings = entryFindings.some((f) => f.severity === 'warn'); + const hasBlockFindings = entryFindings.some((f) => f.severity === ValidationFindingSeverity.Block); + const hasWarnFindings = entryFindings.some((f) => f.severity === ValidationFindingSeverity.Warn); const treeLineClass = showTreeLines && cx(treeChildRowBase, isLastRow ? treeChildRowLast : treeChildRowNotLast); diff --git a/apps/drive-integration/src/locations/Page/components/overview/OverviewSection.tsx b/apps/drive-integration/src/locations/Page/components/overview/OverviewSection.tsx index 8593474cee..69b31e85c1 100644 --- a/apps/drive-integration/src/locations/Page/components/overview/OverviewSection.tsx +++ b/apps/drive-integration/src/locations/Page/components/overview/OverviewSection.tsx @@ -18,6 +18,8 @@ interface OverviewProps { isCtaLoading?: boolean; isCtaDisabled?: boolean; areEntrySelectionsDisabled?: boolean; + /** Whether any block-severity findings exist; drives the acknowledgement Note visibility. */ + hasBlockFindings?: boolean; /** Called with `true` when the user checks the block-findings acknowledgement. */ onBlockFindingsAcknowledged?: (acknowledged: boolean) => void; /** Whether the user has acknowledged block findings. */ @@ -35,6 +37,7 @@ const OverviewSection = ({ isCtaLoading = false, isCtaDisabled = false, areEntrySelectionsDisabled = false, + hasBlockFindings = false, onBlockFindingsAcknowledged, blockFindingsAcknowledged = false, }: OverviewProps) => { @@ -59,8 +62,6 @@ const OverviewSection = ({ return map; }, [payload.validationFindings]); - const hasBlockFindings = (payload.validationFindings ?? []).some((f) => f.severity === 'block'); - return ( <> @@ -72,7 +73,7 @@ const OverviewSection = ({ Review your content and associated entries below. Highlight text to make adjustments. - Select which entries you’d like to create. + Select which entries you'd like to create. diff --git a/apps/drive-integration/src/locations/Page/components/review/ReviewPage.tsx b/apps/drive-integration/src/locations/Page/components/review/ReviewPage.tsx index 985ecb0372..3f1eb23da8 100644 --- a/apps/drive-integration/src/locations/Page/components/review/ReviewPage.tsx +++ b/apps/drive-integration/src/locations/Page/components/review/ReviewPage.tsx @@ -271,6 +271,7 @@ export const ReviewPage = ({ (!hasSelectedEntries || (hasBlockFindings && !blockFindingsAcknowledged)) } areEntrySelectionsDisabled={isMappingDisabled} + hasBlockFindings={hasBlockFindings} blockFindingsAcknowledged={blockFindingsAcknowledged} onBlockFindingsAcknowledged={setBlockFindingsAcknowledged} /> diff --git a/apps/drive-integration/src/types/workflow.ts b/apps/drive-integration/src/types/workflow.ts index 638431ee34..a5f93d51d1 100644 --- a/apps/drive-integration/src/types/workflow.ts +++ b/apps/drive-integration/src/types/workflow.ts @@ -125,8 +125,10 @@ export interface TabsImagesSuspendPayload { tabs?: DocTabOption[]; } -/** Severity levels for validation findings produced by the validate-payload step. */ -export type ValidationFindingSeverity = 'block' | 'warn'; +export enum ValidationFindingSeverity { + Block = 'block', + Warn = 'warn', +} /** A single validation finding from the validate-payload step. */ export interface ValidationFinding { @@ -151,7 +153,6 @@ export interface MappingReviewSuspendPayload { entryBlockGraph: EntryBlockGraph; referenceGraph: ReviewedReferenceGraph; contentTypes: WorkflowContentType[]; - /** Present when the google-docs-agent-improvements flag is on; absent otherwise. */ validationFindings?: ValidationFinding[]; } diff --git a/apps/drive-integration/test/locations/Page/components/overview/OverviewEntryList.spec.tsx b/apps/drive-integration/test/locations/Page/components/overview/OverviewEntryList.spec.tsx index 7129e28440..0b6bfd5821 100644 --- a/apps/drive-integration/test/locations/Page/components/overview/OverviewEntryList.spec.tsx +++ b/apps/drive-integration/test/locations/Page/components/overview/OverviewEntryList.spec.tsx @@ -1,5 +1,6 @@ import { cleanup, render, screen } from '@testing-library/react'; import { afterEach, describe, expect, it, vi } from 'vitest'; +import { ValidationFindingSeverity } from '@types'; import type { ValidationFinding } from '@types'; import type { EntryListRow } from '../../../../../src/utils/overviewEntryList'; import { OverviewEntryList } from '../../../../../src/locations/Page/components/overview/OverviewEntryList'; @@ -36,7 +37,7 @@ describe('OverviewEntryList — validation finding badges (INTEG-4383)', () => { { code: 'required-field-missing', message: 'title missing', - severity: 'block', + severity: ValidationFindingSeverity.Block, entryIndex: 0, }, ]; @@ -49,7 +50,7 @@ describe('OverviewEntryList — validation finding badges (INTEG-4383)', () => { it('renders a "Warning" badge for entries with only warn findings', () => { const rows = [makeRow(0, 'Entry A')]; const findings: ValidationFinding[] = [ - { code: 'displayField-blank', message: 'title blank', severity: 'warn', entryIndex: 0 }, + { code: 'displayField-blank', message: 'title blank', severity: ValidationFindingSeverity.Warn, entryIndex: 0 }, ]; renderList(rows, new Map([[0, findings]])); @@ -63,10 +64,10 @@ describe('OverviewEntryList — validation finding badges (INTEG-4383)', () => { { code: 'required-field-missing', message: 'title missing', - severity: 'block', + severity: ValidationFindingSeverity.Block, entryIndex: 0, }, - { code: 'displayField-blank', message: 'title blank', severity: 'warn', entryIndex: 0 }, + { code: 'displayField-blank', message: 'title blank', severity: ValidationFindingSeverity.Warn, entryIndex: 0 }, ]; renderList(rows, new Map([[0, findings]])); @@ -88,7 +89,7 @@ describe('OverviewEntryList — validation finding badges (INTEG-4383)', () => { { code: 'required-field-missing', message: 'title missing', - severity: 'block', + severity: ValidationFindingSeverity.Block, entryIndex: 1, }, ]; diff --git a/apps/drive-integration/test/locations/Page/components/review/ReviewPage.spec.tsx b/apps/drive-integration/test/locations/Page/components/review/ReviewPage.spec.tsx index 81cacf04de..07f4a3470c 100644 --- a/apps/drive-integration/test/locations/Page/components/review/ReviewPage.spec.tsx +++ b/apps/drive-integration/test/locations/Page/components/review/ReviewPage.spec.tsx @@ -2,8 +2,8 @@ import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/re import { Layout } from '@contentful/f36-components'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import type { PageAppSDK } from '@contentful/app-sdk'; +import { RunStatus, ValidationFindingSeverity } from '@types'; import type { MappingReviewSuspendPayload } from '@types'; -import { RunStatus } from '@types'; import { createMockSDK } from '../../../../mocks'; import { ReviewPage } from '../../../../../src/locations/Page/components/review/ReviewPage'; @@ -138,7 +138,7 @@ describe('ReviewPage — block findings acknowledgement (INTEG-4383)', () => { { code: 'required-field-missing', message: 'title missing', - severity: 'block', + severity: ValidationFindingSeverity.Block, entryIndex: 0, }, ], @@ -155,7 +155,7 @@ describe('ReviewPage — block findings acknowledgement (INTEG-4383)', () => { { code: 'required-field-missing', message: 'title missing', - severity: 'block', + severity: ValidationFindingSeverity.Block, entryIndex: 0, }, ], @@ -174,7 +174,7 @@ describe('ReviewPage — block findings acknowledgement (INTEG-4383)', () => { const payload: MappingReviewSuspendPayload = { ...createPayload(), validationFindings: [ - { code: 'displayField-blank', message: 'title blank', severity: 'warn', entryIndex: 0 }, + { code: 'displayField-blank', message: 'title blank', severity: ValidationFindingSeverity.Warn, entryIndex: 0 }, ], }; renderReviewPage(payload); From 9b356de7afe40b84e10fb1233901346226bc0a9d Mon Sep 17 00:00:00 2001 From: David Shibley Date: Wed, 15 Jul 2026 13:05:59 -0600 Subject: [PATCH 4/4] style(drive-integration): apply prettier formatting [INTEG-4383] Co-Authored-By: Claude Sonnet 4.6 --- .../Page/components/overview/OverviewEntryList.tsx | 4 +++- .../components/overview/OverviewEntryList.spec.tsx | 14 ++++++++++++-- .../Page/components/review/ReviewPage.spec.tsx | 7 ++++++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/apps/drive-integration/src/locations/Page/components/overview/OverviewEntryList.tsx b/apps/drive-integration/src/locations/Page/components/overview/OverviewEntryList.tsx index 233a4f68c8..0b3ebe6b59 100644 --- a/apps/drive-integration/src/locations/Page/components/overview/OverviewEntryList.tsx +++ b/apps/drive-integration/src/locations/Page/components/overview/OverviewEntryList.tsx @@ -50,7 +50,9 @@ function OverviewEntryRowCard({ const isSelected = row.entryIndex === selectedEntryIndex; const isEntrySelectedForCreation = selectedEntryKeys.has(row.id); const entryFindings = findingsByEntryIndex?.get(row.entryIndex) ?? []; - const hasBlockFindings = entryFindings.some((f) => f.severity === ValidationFindingSeverity.Block); + const hasBlockFindings = entryFindings.some( + (f) => f.severity === ValidationFindingSeverity.Block + ); const hasWarnFindings = entryFindings.some((f) => f.severity === ValidationFindingSeverity.Warn); const treeLineClass = diff --git a/apps/drive-integration/test/locations/Page/components/overview/OverviewEntryList.spec.tsx b/apps/drive-integration/test/locations/Page/components/overview/OverviewEntryList.spec.tsx index 0b6bfd5821..46b38185ea 100644 --- a/apps/drive-integration/test/locations/Page/components/overview/OverviewEntryList.spec.tsx +++ b/apps/drive-integration/test/locations/Page/components/overview/OverviewEntryList.spec.tsx @@ -50,7 +50,12 @@ describe('OverviewEntryList — validation finding badges (INTEG-4383)', () => { it('renders a "Warning" badge for entries with only warn findings', () => { const rows = [makeRow(0, 'Entry A')]; const findings: ValidationFinding[] = [ - { code: 'displayField-blank', message: 'title blank', severity: ValidationFindingSeverity.Warn, entryIndex: 0 }, + { + code: 'displayField-blank', + message: 'title blank', + severity: ValidationFindingSeverity.Warn, + entryIndex: 0, + }, ]; renderList(rows, new Map([[0, findings]])); @@ -67,7 +72,12 @@ describe('OverviewEntryList — validation finding badges (INTEG-4383)', () => { severity: ValidationFindingSeverity.Block, entryIndex: 0, }, - { code: 'displayField-blank', message: 'title blank', severity: ValidationFindingSeverity.Warn, entryIndex: 0 }, + { + code: 'displayField-blank', + message: 'title blank', + severity: ValidationFindingSeverity.Warn, + entryIndex: 0, + }, ]; renderList(rows, new Map([[0, findings]])); diff --git a/apps/drive-integration/test/locations/Page/components/review/ReviewPage.spec.tsx b/apps/drive-integration/test/locations/Page/components/review/ReviewPage.spec.tsx index 07f4a3470c..6e475ff897 100644 --- a/apps/drive-integration/test/locations/Page/components/review/ReviewPage.spec.tsx +++ b/apps/drive-integration/test/locations/Page/components/review/ReviewPage.spec.tsx @@ -174,7 +174,12 @@ describe('ReviewPage — block findings acknowledgement (INTEG-4383)', () => { const payload: MappingReviewSuspendPayload = { ...createPayload(), validationFindings: [ - { code: 'displayField-blank', message: 'title blank', severity: ValidationFindingSeverity.Warn, entryIndex: 0 }, + { + code: 'displayField-blank', + message: 'title blank', + severity: ValidationFindingSeverity.Warn, + entryIndex: 0, + }, ], }; renderReviewPage(payload);