Skip to content
Draft
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
5 changes: 3 additions & 2 deletions src/analysis/individualStudy/stats/TrialVisualization.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ import {
import { useMemo } from 'react';
import { ParticipantData } from '../../../storage/types';
import { StudyConfig } from '../../../parser/types';
import { studyComponentToIndividualComponent } from '../../../utils/handleComponentInheritance';
import { getComponentName, studyComponentToIndividualComponent } from '../../../utils/handleComponentInheritance';
import { ResponseVisualization } from './ResponseVisualization';

export function TrialVisualization({
participantData, studyConfig, trialId,
}: {
participantData: ParticipantData[]; studyConfig: StudyConfig, trialId?: string;
}) {
const trialConfig = trialId && trialId !== 'end' && studyComponentToIndividualComponent(studyConfig.components[trialId], studyConfig);
const componentName = getComponentName(trialId || '');
const trialConfig = trialId && trialId !== 'end' && studyComponentToIndividualComponent(studyConfig.components[componentName], studyConfig);

const items = useMemo(() => [
{ id: 'Config and Timing', type: 'metadata' } as { id: 'Config and Timing', type: 'metadata'},
Expand Down
22 changes: 13 additions & 9 deletions src/analysis/individualStudy/summary/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,23 @@ import { Response, StudyConfig } from '../../../parser/types';
import { componentAnswersAreCorrect } from '../../../utils/correctAnswer';
import { studyComponentToIndividualComponent } from '../../../utils/handleComponentInheritance';

function filterParticipants(visibleParticipants: ParticipantData[], componentName?: string, excludeRejected?: boolean): ParticipantData[] {
function filterParticipants(visibleParticipants: ParticipantData[], identifier?: string, excludeRejected?: boolean): ParticipantData[] {
return visibleParticipants.filter((participant) => {
// Filter out rejected participants if excludeRejected is true
const isNotRejected = !excludeRejected || !participant.rejected;

// Filter by component - participant must have an answer for the component and has finished it
const hasValidComponentAnswer = !componentName || Object.values(participant.answers).some((answer) => answer.componentName === componentName && answer.startTime > 0 && answer.endTime !== -1);
const hasValidComponentAnswer = !identifier || Object.values(participant.answers).some((answer) => {
const cleanAnswerIdentifier = answer.identifier.replace(/_[^_]*$/, '');
return cleanAnswerIdentifier === identifier && answer.startTime > 0 && answer.endTime !== -1;
});

return isNotRejected && hasValidComponentAnswer;
});
}

function calculateParticipantCounts(visibleParticipants: ParticipantData[], componentName?: string): ParticipantCounts {
const filteredParticipants = filterParticipants(visibleParticipants, componentName, false);
function calculateParticipantCounts(visibleParticipants: ParticipantData[], identifier?: string): ParticipantCounts {
const filteredParticipants = filterParticipants(visibleParticipants, identifier, false);

const participantCounts: ParticipantCounts = {
total: filteredParticipants.length,
Expand All @@ -33,9 +36,9 @@ function calculateParticipantCounts(visibleParticipants: ParticipantData[], comp
return participantCounts;
}

function calculateDateStats(visibleParticipants: ParticipantData[], componentName?: string): { startDate: Date | null; endDate: Date | null } {
function calculateDateStats(visibleParticipants: ParticipantData[], identifier?: string): { startDate: Date | null; endDate: Date | null } {
// Filter out rejected participants and filter by component if provided
const filteredParticipants = filterParticipants(visibleParticipants, componentName, true);
const filteredParticipants = filterParticipants(visibleParticipants, identifier, true);
const answers = filteredParticipants
.flatMap((participant) => Object.values(participant.answers))
.filter((answer) => answer.endTime !== -1);
Expand All @@ -53,15 +56,16 @@ function calculateDateStats(visibleParticipants: ParticipantData[], componentNam
};
}

function calculateTimeStats(visibleParticipants: ParticipantData[], componentName?: string): { avgTime: number; avgCleanTime: number; participantsWithInvalidCleanTimeCount: number } {
function calculateTimeStats(visibleParticipants: ParticipantData[], identifier?: string): { avgTime: number; avgCleanTime: number; participantsWithInvalidCleanTimeCount: number } {
// Filter out rejected participants and filter by component if provided
const filteredParticipants = filterParticipants(visibleParticipants, componentName, true);
const filteredParticipants = filterParticipants(visibleParticipants, identifier, true);

let participantsWithInvalidCleanTimeCount = 0;

const time = filteredParticipants.reduce((acc, participant) => {
let hasInvalidCleanTime = false;
const timeStats = Object.values(participant.answers)
.filter((answer) => (!componentName || answer.componentName === componentName) && answer.endTime !== -1)
.filter((answer) => (!identifier || answer.identifier.replace(/_[^_]*$/, '') === identifier) && answer.endTime !== -1)
.map((answer) => {
const cleanedDuration = getCleanedDuration(answer as never);
if (cleanedDuration === -1) {
Expand Down
4 changes: 2 additions & 2 deletions src/analysis/individualStudy/thinkAloud/ThinkAloudFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ export function ThinkAloudFooter({
setSearchParams({ participantId, currentTrial: Object.entries(participant.answers).find(([_, ans]) => +ans.trialOrder.split('_')[0] === 0)?.[0] || '' });
}

return participant?.answers[currentTrial]?.componentName ?? '';
return participant?.answers[currentTrial]?.identifier.replace(/_[^_]*$/, '') ?? '';
}, [currentTrial, participant, participantId, setSearchParams]);

const xScale = useMemo(() => {
Expand Down Expand Up @@ -416,7 +416,7 @@ export function ThinkAloudFooter({
// this needs to be in a helper or two which we dont currently have
onChange={(e: string | null) => {
if (participant && e) {
const trial = Object.entries(participant.answers).find(([_key, ans]) => +ans.trialOrder.split('_')[0] === getSequenceFlatMap(participant?.sequence).indexOf(e))?.[0] || '';
const trial = Object.entries(participant.answers).find(([_key, ans]) => +ans.trialOrder.split('_')[0] === getSequenceFlatMap(participant?.sequence, '').indexOf(e))?.[0] || '';
localStorage.setItem('currentTrial', trial);

setSearchParams({ participantId, currentTrial: trial });
Expand Down
6 changes: 3 additions & 3 deletions src/components/StepRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { WindowEventsContext } from '../store/hooks/useWindowEvents';
import { useStoreSelector, useStoreDispatch, useStoreActions } from '../store/store';
import { AnalysisFooter } from './interface/AnalysisFooter';
import { useIsAnalysis } from '../store/hooks/useIsAnalysis';
import { studyComponentToIndividualComponent } from '../utils/handleComponentInheritance';
import { getComponentName, studyComponentToIndividualComponent } from '../utils/handleComponentInheritance';
import { useCurrentComponent } from '../routes/utils';
import { ResolutionWarning } from './interface/ResolutionWarning';
import { useFetchStylesheet } from '../utils/fetchStylesheet';
Expand All @@ -35,7 +35,7 @@ export function StepRenderer() {
const studyConfig = useStudyConfig();
const currentComponent = useCurrentComponent();

const componentConfig = useMemo(() => studyComponentToIndividualComponent(studyConfig.components[currentComponent] || {}, studyConfig), [currentComponent, studyConfig]);
const componentConfig = useMemo(() => studyComponentToIndividualComponent(studyConfig.components[getComponentName(currentComponent)] || {}, studyConfig), [currentComponent, studyConfig]);

const windowEventDebounceTime = useMemo(() => componentConfig.windowEventDebounceTime ?? studyConfig.uiConfig.windowEventDebounceTime ?? 100, [componentConfig, studyConfig]);

Expand Down Expand Up @@ -130,7 +130,7 @@ export function StepRenderer() {
const { developmentModeEnabled, dataCollectionEnabled } = useMemo(() => modes, [modes]);

// No default value for withSidebar since it's a required field in uiConfig
const sidebarOpen = useMemo(() => (((analysisHasScreenRecording && analysisCanPlayScreenRecording) || currentComponent === 'end') ? false : (componentConfig.withSidebar ?? studyConfig.uiConfig.withSidebar)), [analysisHasScreenRecording, analysisCanPlayScreenRecording, currentComponent, componentConfig.withSidebar, studyConfig.uiConfig.withSidebar]);
const sidebarOpen = useMemo(() => (((analysisHasScreenRecording && analysisCanPlayScreenRecording) || getComponentName(currentComponent) === 'end') ? false : (componentConfig.withSidebar ?? studyConfig.uiConfig.withSidebar)), [analysisHasScreenRecording, analysisCanPlayScreenRecording, currentComponent, componentConfig.withSidebar, studyConfig.uiConfig.withSidebar]);
const sidebarWidth = useMemo(() => componentConfig?.sidebarWidth ?? studyConfig.uiConfig.sidebarWidth ?? 300, [componentConfig, studyConfig]);
const showTitleBar = useMemo(() => componentConfig.showTitleBar ?? studyConfig.uiConfig.showTitleBar ?? true, [componentConfig, studyConfig]);

Expand Down
4 changes: 2 additions & 2 deletions src/components/interface/AppHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { calculateProgressData } from '../../storage/engines/utils';
import { PREFIX } from '../../utils/Prefix';
import { getNewParticipant } from '../../utils/nextParticipant';
import { RecordingAudioWaveform } from './RecordingAudioWaveform';
import { studyComponentToIndividualComponent } from '../../utils/handleComponentInheritance';
import { getComponentName, studyComponentToIndividualComponent } from '../../utils/handleComponentInheritance';
import { useRecordingContext } from '../../store/hooks/useRecording';

export function AppHeader({ developmentModeEnabled, dataCollectionEnabled }: { developmentModeEnabled: boolean; dataCollectionEnabled: boolean }) {
Expand All @@ -50,7 +50,7 @@ export function AppHeader({ developmentModeEnabled, dataCollectionEnabled }: { d
const { storageEngine } = useStorageEngine();

const currentComponent = useCurrentComponent();
const componentConfig = useMemo(() => studyComponentToIndividualComponent(studyConfig.components[currentComponent] || {}, studyConfig), [currentComponent, studyConfig]);
const componentConfig = useMemo(() => studyComponentToIndividualComponent(studyConfig.components[getComponentName(currentComponent)] || {}, studyConfig), [currentComponent, studyConfig]);

const currentStep = useCurrentStep();

Expand Down
4 changes: 2 additions & 2 deletions src/components/interface/AppNavBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import { useStudyConfig } from '../../store/hooks/useStudyConfig';
import { useStoredAnswer } from '../../store/hooks/useStoredAnswer';
import { ResponseBlock } from '../response/ResponseBlock';
import { useCurrentComponent } from '../../routes/utils';
import { studyComponentToIndividualComponent } from '../../utils/handleComponentInheritance';
import { getComponentName, studyComponentToIndividualComponent } from '../../utils/handleComponentInheritance';

export function AppNavBar({ width, top, sidebarOpen }: { width: number, top: number, sidebarOpen: boolean }) {
// Get the config for the current step
const studyConfig = useStudyConfig();
const currentComponent = useCurrentComponent();
const stepConfig = studyConfig.components[currentComponent];
const stepConfig = studyConfig.components[getComponentName(currentComponent)];

const currentConfig = useMemo(() => {
if (stepConfig) {
Expand Down
19 changes: 14 additions & 5 deletions src/components/interface/StepsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ import {
import { useNavigate, useLocation } from 'react-router';
import { ParticipantData, Response, StudyConfig } from '../../parser/types';
import { Sequence, StoredAnswer } from '../../store/types';
import { addPathToComponentBlock } from '../../utils/getSequenceFlatMap';
import { addPathToComponentBlock, getSequenceFlatMap } from '../../utils/getSequenceFlatMap';
import { useStudyId } from '../../routes/utils';
import { encryptIndex } from '../../utils/encryptDecryptIndex';
import { isDynamicBlock } from '../../parser/utils';
import { componentAnswersAreCorrect } from '../../utils/correctAnswer';
import { getComponentName } from '../../utils/handleComponentInheritance';

function hasRandomization(responses: Response[]) {
return responses.some((response) => {
Expand Down Expand Up @@ -98,6 +99,7 @@ type ComponentStepItem = StepItemBase & {
isLibraryImport: boolean;
importedLibraryName?: string;
component?: StudyConfig['components'][string];
sequenceName: string;
componentAnswer?: StoredAnswer;
componentName: string; // Full component name (e.g., package.components.ComponentName)
isExcluded?: boolean; // Component was excluded from participant sequence
Expand Down Expand Up @@ -257,13 +259,15 @@ export function StepsPanel({
let newFlatTree: StepItem[] = [];
if (participantSequence === undefined) {
// Browse Components
newFlatTree = Object.keys(studyConfig.components).map((key) => {
newFlatTree = getSequenceFlatMap(studyConfig.sequence).map((key) => {
const {
label,
isLibraryImport,
importedLibraryName,
} = parseLibraryComponentReference(key);

const componentName = getComponentName(key);

return {
type: 'component',
label,
Expand All @@ -272,7 +276,8 @@ export function StepsPanel({
href: `/${studyId}/reviewer-${key}`,
isLibraryImport,
importedLibraryName,
componentName: key,
componentName,
sequenceName: key,
};
});
} else {
Expand Down Expand Up @@ -313,6 +318,7 @@ export function StepsPanel({
component: studyConfig.components[node],
componentAnswer: participantAnswers[componentIdentifier],
componentName: node,
sequenceName: node,
});

if (dynamic) {
Expand Down Expand Up @@ -417,6 +423,7 @@ export function StepsPanel({
component: studyConfig.components[excludedComponent],
componentName: excludedComponent,
isExcluded: true,
sequenceName: excludedComponent,
});
});

Expand Down Expand Up @@ -472,6 +479,7 @@ export function StepsPanel({
component: studyConfig.components[child],
componentName: child,
isExcluded: true,
sequenceName: child,
});
} else {
const childBlockPath = `${excludedParentPath}.${child.id ?? child.order}_excluded`;
Expand Down Expand Up @@ -544,7 +552,7 @@ export function StepsPanel({
// Set full and rendered flat tree
setFullFlatTree(newFlatTree);
setRenderedFlatTree(newFlatTree);
}, [fullOrder, participantAnswers, participantSequence, studyConfig.components, studyId]);
}, [fullOrder, participantAnswers, participantSequence, studyConfig, studyConfig.components, studyId]);

const collapseBlock = useCallback((startIndex: number, startItem: StepItem) => {
setRenderedFlatTree((prevRenderedFlatTree) => {
Expand Down Expand Up @@ -680,6 +688,7 @@ export function StepsPanel({
component,
componentAnswer,
componentName,
sequenceName,
isLibraryImport: isComponentLibraryImport,
importedLibraryName: componentImportedLibraryName,
} = (comp ?? {}) as Partial<ComponentStepItem>;
Expand Down Expand Up @@ -740,7 +749,7 @@ export function StepsPanel({
onClick={() => {
if (isComponent && href && !isExcluded) {
if (isAnalysis) {
navigate(`/analysis/stats/${studyId}/stats/${encodeURIComponent(String(componentName))}`);
navigate(`/analysis/stats/${studyId}/stats/${encodeURIComponent(String(sequenceName))}`);
} else {
navigate(`${href}${location.search}`);
}
Expand Down
4 changes: 2 additions & 2 deletions src/controllers/ComponentController.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { findBlockForStep } from '../utils/getSequenceFlatMap';
import { VegaController, VegaProvState } from './VegaController';
import { useIsAnalysis } from '../store/hooks/useIsAnalysis';
import { VideoController } from './VideoController';
import { studyComponentToIndividualComponent } from '../utils/handleComponentInheritance';
import { getComponentName, studyComponentToIndividualComponent } from '../utils/handleComponentInheritance';
import { useFetchStylesheet } from '../utils/fetchStylesheet';
import { ScreenRecordingReplay } from '../components/screenRecording/ScreenRecordingReplay';
import { decryptIndex, encryptIndex } from '../utils/encryptDecryptIndex';
Expand All @@ -45,7 +45,7 @@ export function ComponentController() {
const currentComponent = useCurrentComponent();
const studyId = useStudyId();

const stepConfig = studyConfig.components[currentComponent];
const stepConfig = studyConfig.components[getComponentName(currentComponent)];
const { storageEngine } = useStorageEngine();

const answers = useStoreSelector((store) => store.answers);
Expand Down
6 changes: 3 additions & 3 deletions src/routes/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { decryptIndex, encryptIndex } from '../utils/encryptDecryptIndex';
import { JumpFunctionParameters, JumpFunctionReturnVal } from '../store/types';
import { findFuncBlock } from '../utils/getSequenceFlatMap';
import { useStudyConfig } from '../store/hooks/useStudyConfig';
import { getComponent } from '../utils/handleComponentInheritance';
import { getComponent, getComponentName } from '../utils/handleComponentInheritance';

export function useStudyId(): string {
const { studyId } = useParams();
Expand Down Expand Up @@ -54,7 +54,7 @@ export function useCurrentComponent(): string {

const [indexWhenSettingComponentName, setIndexWhenSettingComponentName] = useState<number | null>(null);

const currentComponent = useMemo(() => (typeof currentStep === 'number' ? getComponent(flatSequence[currentStep], studyConfig) : currentStep.includes('reviewer-') || currentStep.startsWith('__') ? currentStep : null), [currentStep, flatSequence, studyConfig]);
const currentComponent = useMemo(() => (typeof currentStep === 'number' ? getComponent(getComponentName(flatSequence[currentStep]), studyConfig) : currentStep.includes('reviewer-') || currentStep.startsWith('__') ? currentStep : null), [currentStep, flatSequence, studyConfig]);

const [compName, setCompName] = useState('__dynamicLoading');

Expand Down Expand Up @@ -82,7 +82,7 @@ export function useCurrentComponent(): string {

useEffect(() => {
if (typeof currentStep === 'number') {
const component = getComponent(flatSequence[currentStep], studyConfig);
const component = getComponent(getComponentName(flatSequence[currentStep]), studyConfig);

const funcName = flatSequence[currentStep];
const decryptedFuncIndex = funcIndex ? decryptIndex(funcIndex) : 0;
Expand Down
4 changes: 3 additions & 1 deletion src/store/hooks/useNextStep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ import {
import { decryptIndex, encryptIndex } from '../../utils/encryptDecryptIndex';
import { useIsAnalysis } from './useIsAnalysis';
import { componentAnswersAreCorrect } from '../../utils/correctAnswer';
import { getComponentName } from '../../utils/handleComponentInheritance';

function checkAllAnswersCorrect(answers: StoredAnswer['answer'], componentId: string, componentConfig: IndividualComponent | InheritedComponent, studyConfig: StudyConfig) {
const componentName = componentId.slice(0, componentId.lastIndexOf('_'));
const component = componentId.slice(0, componentId.lastIndexOf('_'));
const componentName = getComponentName(component);

// Find the matching component in the study config
const foundConfigComponent = Object.entries(studyConfig.components).find(([configComponentId]) => configComponentId === componentName);
Expand Down
Loading
Loading