Skip to content
Merged
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
21 changes: 10 additions & 11 deletions apps/azure/src/components/editor/InvestigationWorkspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ import {
} from '@variscout/core';
import { detectEvidenceClusters } from '@variscout/core/findings';
import type { ColumnTypeMap } from '@variscout/core/findings';
import { canAccess } from '@variscout/core/projectMembership';
import type { ProjectMember } from '@variscout/core/projectMembership';
import { detectColumns } from '@variscout/core/parser';
import { detectInvestigationPhase } from '@variscout/core/ai';
import { resolveMode, getStrategy } from '@variscout/core/strategy';
Expand Down Expand Up @@ -99,8 +101,10 @@ interface InvestigationWorkspaceProps {
attachment?: File
) => void | Promise<void>;
handleAddPhoto: ((findingId: string, commentId: string, file: File) => Promise<void>) | undefined;
handleCaptureFromTeams: ((findingId: string, commentId: string) => Promise<void>) | undefined;
isTeamsCamera: boolean;
/** userId of the currently signed-in user — used for canAccess('edit-approach') role check. */
userId: string | null;
/** Members of the active improvement project — used for canAccess role check. Empty = open-access (quick-analysis flow). */
members: ProjectMember[];
// AI
aiOrch: UseAIOrchestrationReturn;
actionProposalsState: UseActionProposalsReturn;
Expand Down Expand Up @@ -144,8 +148,8 @@ export const InvestigationWorkspace: React.FC<InvestigationWorkspaceProps> = ({
handleProjectIdea,
handleAddCommentWithAuthor,
handleAddPhoto,
handleCaptureFromTeams,
isTeamsCamera,
userId,
members,
aiOrch,
actionProposalsState,
handleSearchKnowledge,
Expand Down Expand Up @@ -969,19 +973,14 @@ export const InvestigationWorkspace: React.FC<InvestigationWorkspaceProps> = ({
columnAliases={columnAliases}
activeFindingId={highlightedFindingId}
onAddPhoto={
(members.length === 0 ||
(userId !== null && canAccess(userId, members, 'edit-approach'))) &&
handleAddPhoto
? (fId: string, cId: string, file: File) => {
handleAddPhoto(fId, cId, file);
}
: undefined
}
onCaptureFromTeams={
isTeamsCamera && handleCaptureFromTeams
? (fId: string, cId: string) => {
handleCaptureFromTeams(fId, cId);
}
: undefined
}
onCreateQuestion={handleCreateQuestion}
questionsMap={questionsMap}
onSetValidationTask={questionsState.setValidationTask}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ const showCharterMock = vi.hoisted(() => vi.fn());
const capturedWallCanvasProps = vi.hoisted(() => ({
current: null as Record<string, unknown> | null,
}));
const capturedFindingsLogProps = vi.hoisted(() => ({
current: null as Record<string, unknown> | null,
}));

vi.mock('@variscout/charts', async importOriginal => {
const actual = await importOriginal<typeof import('@variscout/charts')>();
Expand Down Expand Up @@ -79,7 +82,10 @@ vi.mock('@variscout/ui', async importOriginal => {
QuestionChecklist: () => <div data-testid="question-checklist" />,
InvestigationPhaseBadge: () => null,
InvestigationConclusion: () => null,
FindingsLog: () => <div data-testid="findings-log" />,
FindingsLog: (props: Record<string, unknown>) => {
capturedFindingsLogProps.current = props;
return <div data-testid="findings-log" />;
},
QuestionLinkPrompt: () => null,
useWallIsMobile: () => false,
WallCanvas: (props: { hubs: unknown[]; planningProps?: Record<string, unknown> }) => {
Expand Down Expand Up @@ -187,6 +193,7 @@ vi.mock('@variscout/core/stats', () => ({

import { getCanvasViewportInitialState, useCanvasViewportStore } from '@variscout/stores';
import { RETURN_NAVIGATION_STORAGE_KEY } from '@variscout/hooks';
import { usePanelsStore } from '../../../features/panels/panelsStore';
import { InvestigationWorkspace } from '../InvestigationWorkspace';

// ── 3. Minimal props factory ───────────────────────────────────────────────
Expand Down Expand Up @@ -228,8 +235,8 @@ function makeMinimalProps(): React.ComponentProps<typeof InvestigationWorkspace>
handleProjectIdea: noOp,
handleAddCommentWithAuthor: noOp as never,
handleAddPhoto: undefined,
handleCaptureFromTeams: undefined,
isTeamsCamera: false,
userId: 'lead@org',
members: [],
aiOrch: {
handleAskCoScoutFromCategory: noOp,
} as never,
Expand Down Expand Up @@ -373,4 +380,78 @@ describe('InvestigationWorkspace Map/Wall toggle', () => {
expect(capturedWallCanvasProps.current?.planningProps).toBeUndefined();
});
});

describe('canAccess photo gate (wedge spec §3.1 — Sponsor is read-only)', () => {
const makeMember = (userId: string, role: 'lead' | 'member' | 'sponsor') => ({
id: `pm-${userId}`,
createdAt: 1,
deletedAt: null,
userId,
displayName: userId,
role,
invitedAt: 1,
});

beforeEach(() => {
capturedFindingsLogProps.current = null;
// Switch to 'findings' view so FindingsLog renders (not InvestigationMapView)
usePanelsStore.getState().setInvestigationViewMode('findings');
});

it('Lead member receives onAddPhoto when handleAddPhoto is provided', () => {
const handleAddPhoto = vi.fn();
const props = makeMinimalProps();
render(
<InvestigationWorkspace
{...props}
handleAddPhoto={handleAddPhoto}
userId="lead@org"
members={[makeMember('lead@org', 'lead')]}
/>
);
expect(capturedFindingsLogProps.current?.onAddPhoto).toBeDefined();
});

it('Sponsor member receives onAddPhoto=undefined even when handleAddPhoto is provided', () => {
const handleAddPhoto = vi.fn();
const props = makeMinimalProps();
render(
<InvestigationWorkspace
{...props}
handleAddPhoto={handleAddPhoto}
userId="sponsor@org"
members={[makeMember('sponsor@org', 'sponsor')]}
/>
);
expect(capturedFindingsLogProps.current?.onAddPhoto).toBeUndefined();
});

it('null userId always yields onAddPhoto=undefined', () => {
const handleAddPhoto = vi.fn();
const props = makeMinimalProps();
render(
<InvestigationWorkspace
{...props}
handleAddPhoto={handleAddPhoto}
userId={null}
members={[makeMember('lead@org', 'lead')]}
/>
);
expect(capturedFindingsLogProps.current?.onAddPhoto).toBeUndefined();
});

it('empty members array (quick-analysis flow) receives onAddPhoto regardless of role check', () => {
const handleAddPhoto = vi.fn();
const props = makeMinimalProps();
render(
<InvestigationWorkspace
{...props}
handleAddPhoto={handleAddPhoto}
userId="any@org"
members={[]}
/>
);
expect(capturedFindingsLogProps.current?.onAddPhoto).toBeDefined();
});
});
});
34 changes: 0 additions & 34 deletions apps/azure/src/components/views/ReportView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,9 @@ import {
import IChart from '../charts/IChart';
import Boxplot from '../charts/Boxplot';
import ParetoChart from '../charts/ParetoChart';
import { usePublishReport } from '../../hooks/usePublishReport';

interface ReportViewProps {
onClose: () => void;
onShareReport?: () => void;
canShareViaTeams?: boolean;
// AI enhancement
aiEnabled?: boolean;
narrative?: string | null;
Expand Down Expand Up @@ -149,8 +146,6 @@ const REPORT_MAP_SIZE = { width: REPORT_CHART_MAX_WIDTH, height: 400 } as const;

const ReportView: React.FC<ReportViewProps> = ({
onClose,
onShareReport,
canShareViaTeams,
aiEnabled,
narrative,
activeIPScope,
Expand Down Expand Up @@ -678,26 +673,6 @@ const ReportView: React.FC<ReportViewProps> = ({
? `IP Report: ${activeIPScope.title}`
: processContext?.issueStatement || outcome || 'Analysis';

// Publish to SharePoint (ADR-026)
const {
publish,
publishReplace,
status: publishStatus,
error: publishError,
publishedUrl,
reset: publishReset,
} = usePublishReport({
projectName: processName,
processName: processContext?.description,
reportType,
sections: derivedSections,
questions: reportQuestions,
processContext: processContext ?? undefined,
stats: stats ?? undefined,
sampleCount: filteredData.length,
aiNarrative: narrative ?? undefined,
});

// ---------------------------------------------------------------------------
// Print / Save as PDF
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -1434,14 +1409,6 @@ const ReportView: React.FC<ReportViewProps> = ({
onScrollToSection={handleScrollToSection}
renderSection={renderSection}
onPrintReport={handlePrint}
onShareReport={onShareReport ?? (() => undefined)}
shareLinkGate="available"
onPublishToSharePoint={publish}
onPublishReplace={publishReplace}
publishStatus={publishStatus}
publishError={publishError}
onPublishReset={publishReset}
publishedUrl={publishedUrl}
onClose={onClose}
activeIPContextChip={
activeIPTitle && onOpenActiveIP && onExitActiveIP ? (
Expand All @@ -1452,7 +1419,6 @@ const ReportView: React.FC<ReportViewProps> = ({
/>
) : null
}
canShareViaTeams={canShareViaTeams}
/>
</div>
</ErrorBoundary>
Expand Down
61 changes: 0 additions & 61 deletions apps/azure/src/hooks/usePublishReport.ts

This file was deleted.

26 changes: 0 additions & 26 deletions apps/azure/src/hooks/useShareReport.ts

This file was deleted.

15 changes: 7 additions & 8 deletions apps/azure/src/pages/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1135,12 +1135,11 @@ export const Editor: React.FC<EditorProps> = ({
});

// Photo comments
const { handleAddPhoto, handleCaptureFromTeams, isTeamsCamera, handleAddCommentWithAuthor } =
usePhotoComments({
findingsState,
analysisId: currentProjectName || 'default',
author: currentUser?.name,
});
const { handleAddPhoto, handleAddCommentWithAuthor } = usePhotoComments({
findingsState,
analysisId: currentProjectName || 'default',
author: currentUser?.name,
});

// Question CRUD
const questionsState = useQuestions({
Expand Down Expand Up @@ -1867,8 +1866,8 @@ export const Editor: React.FC<EditorProps> = ({
handleProjectIdea={handleProjectIdea}
handleAddCommentWithAuthor={handleAddCommentWithAuthor}
handleAddPhoto={handleAddPhoto}
handleCaptureFromTeams={isTeamsCamera ? handleCaptureFromTeams : undefined}
isTeamsCamera={isTeamsCamera}
userId={currentUser?.email ?? null}
members={wallActiveIPMembers}
aiOrch={aiOrch}
actionProposalsState={actionProposalsState}
handleSearchKnowledge={handleSearchKnowledge}
Expand Down
2 changes: 0 additions & 2 deletions apps/pwa/src/components/views/ReportView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -534,8 +534,6 @@ const ReportView: React.FC<ReportViewProps> = ({
renderSection={renderSection}
onCopyAllCharts={handleCopyAllCharts}
onPrintReport={handlePrintReport}
shareLinkGate="locked"
onShareReport={() => undefined}
onClose={onClose}
activeIPContextChip={
activeIPTitle && onOpenActiveIP && onExitActiveIP ? (
Expand Down
Loading