Skip to content

Commit f2ade4d

Browse files
committed
segment section: have a single object create setion and widget config
part of #501 Define 2 interfaces: `SectionConfigFactory` and `WidgetConfigFactory`, the former extending the latter. Config objects that provide more than one widget can use classes implementing those factories to return all the required widgets. The `sectionSegments`, as well as `groupPersonTrips` and `groupSegments` use this pattern to advertise all their widgets and configs. The demo_survey platform now gets the sections and widgets from a single `questionnaire.ts` file, which, for the segment section, now use the `SegmentsSectionFactory` to get all the builtin section config and widgets. Builtin widgets do not have to be added to the `segments.tsx` file anymore.
1 parent ae06dc1 commit f2ade4d

27 files changed

+1272
-872
lines changed

example/demo_survey/src/admin/app-admin.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import runClientApp from 'evolution-frontend/lib/apps/admin';
22
import { setApplicationConfiguration } from 'chaire-lib-frontend/lib/config/application.config';
33
import appConfig, { EvolutionApplicationConfiguration } from 'evolution-frontend/lib/config/application.config';
44

5-
import surveySections from '../survey/sections';
6-
import * as widgetsConfig from '../survey/widgets';
5+
import { surveySections, widgetsConfig } from '../survey/questionnaire';
76
import projectHelpers from '../survey/helper';
87
import VisitedPlaceSection from '../survey/templates/VisitedPlacesSection';
98

example/demo_survey/src/app-survey.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import runClientApp from 'evolution-frontend/lib/apps/participant';
22
import appConfig, { EvolutionApplicationConfiguration } from 'evolution-frontend/lib/config/application.config';
33
import { setApplicationConfiguration } from 'chaire-lib-frontend/lib/config/application.config';
44

5-
import surveySections from './survey/sections';
6-
import * as widgetsConfig from './survey/widgets';
5+
import { surveySections, widgetsConfig } from './survey/questionnaire';
76
import projectHelpers from './survey/helper';
87
import VisitedPlaceSection from './survey/templates/VisitedPlacesSection';
98

example/demo_survey/src/survey/helper.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { Mode } from 'evolution-common/lib/services/odSurvey/types';
2222
// Configuration for the segments section
2323
// FIXME As sections and their widgets become more builtin, this thould be moved elsewhere. For now, it just needs to be available for both widgets.ts and sections.ts files
2424
export const segmentSectionConfig = {
25+
type: 'segments' as const,
2526
enabled: true,
2627
modesIncludeOnly: [
2728
'walk',
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { SegmentsSectionFactory } from 'evolution-common/lib/services/questionnaire/sections/segments/sectionSegments';
2+
import * as odSurveyHelper from 'evolution-common/lib/services/odSurvey/helpers';
3+
import surveySections from './sections';
4+
import * as widgetsConfig from './widgets';
5+
import helper, { segmentSectionConfig } from './helper';
6+
import { getAndValidateSurveySections, SectionConfig } from 'evolution-common/lib/services/questionnaire/types';
7+
import { getFormattedDate, validateButtonActionWithCompleteSection } from 'evolution-frontend/lib/services/display/frontendHelper';
8+
import { faCheckCircle } from '@fortawesome/free-solid-svg-icons/faCheckCircle';
9+
10+
// FIXME For now this file is here and has quite a bit of code. Eventually, the
11+
// questionnaire generation should be done in Evolution directly when we have
12+
// more builtin stuff
13+
14+
const widgetFactoryOptions = {
15+
getFormattedDate: getFormattedDate,
16+
buttonActions: { validateButtonAction: validateButtonActionWithCompleteSection },
17+
iconMapper: { 'check-circle': faCheckCircle }
18+
};
19+
20+
const segmentSectionConfigFactory = new SegmentsSectionFactory(segmentSectionConfig, widgetFactoryOptions);
21+
const segmentSectionConfigFromFactory = segmentSectionConfigFactory.getSectionConfig();
22+
23+
// Add the segments section to the exported configuration
24+
const segmentConfig: SectionConfig = {
25+
...segmentSectionConfigFromFactory,
26+
isSectionVisible: function(interview) {
27+
const person = odSurveyHelper.getPerson({ interview }) as any;
28+
return person && person.didTripsOnTripsDate === 'yes';
29+
},
30+
isSectionCompleted: (interview) => {
31+
const person = odSurveyHelper.getPerson({ interview });
32+
return helper.tripsForPersonComplete(person, interview);
33+
}
34+
};
35+
// FIXME Workaround to satisfy the completion percentage calculation that expects sections to be defined in their order of display in the object (see https://github.com/chairemobilite/evolution/issues/1024)
36+
const { travelBehavior, end, completed, ...earlierSections } = surveySections;
37+
const validatedSections = getAndValidateSurveySections({
38+
...earlierSections,
39+
segments: segmentConfig,
40+
travelBehavior,
41+
end,
42+
completed
43+
});
44+
45+
// Widgets defined in the interview will override the ones from the section factory, if any
46+
const allWidgetConfig = Object.assign({}, segmentSectionConfigFactory.getWidgetConfigs(), widgetsConfig);
47+
48+
export { validatedSections as surveySections, allWidgetConfig as widgetsConfig };

example/demo_survey/src/survey/sections.ts

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ import isEmpty from 'lodash/isEmpty';
1010
import { _isBlank } from 'chaire-lib-common/lib/utils/LodashExtensions';
1111
import { getResponse, getValidation, addGroupedObjects, removeGroupedObjects } from 'evolution-common/lib/utils/helpers';
1212
import * as odSurveyHelper from 'evolution-common/lib/services/odSurvey/helpers';
13-
import { getSegmentsSectionConfig } from 'evolution-common/lib/services/questionnaire/sections/segments/sectionSegments';
14-
import helper, { segmentSectionConfig } from './helper';
13+
import helper from './helper';
1514
import config from 'chaire-lib-common/lib/config/shared/project.config';
1615
import { getAndValidateSurveySections, SectionConfig } from 'evolution-common/lib/services/questionnaire/types';
1716
import { personNoWorkTripReason, personNoSchoolTripReason, personWhoAnsweredForThisPerson } from './widgets/travelBehavior';
@@ -406,18 +405,6 @@ const sections: { [sectionName: string]: SectionConfig } = {
406405
}
407406
},
408407

409-
segments: {
410-
...getSegmentsSectionConfig({ segmentConfig: segmentSectionConfig }),
411-
isSectionVisible: function(interview) {
412-
const person = odSurveyHelper.getPerson({ interview }) as any;
413-
return person && person.didTripsOnTripsDate === 'yes';
414-
},
415-
isSectionCompleted: (interview) => {
416-
const person = odSurveyHelper.getPerson({ interview });
417-
return helper.tripsForPersonComplete(person, interview);
418-
}
419-
},
420-
421408
travelBehavior: {
422409
previousSection: 'segments',
423410
nextSection: "personsTrips",
@@ -539,4 +526,4 @@ const sections: { [sectionName: string]: SectionConfig } = {
539526

540527
};
541528

542-
export default getAndValidateSurveySections(sections);
529+
export default sections;

example/demo_survey/src/survey/widgets.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { faCheckCircle } from '@fortawesome/free-solid-svg-icons/faCheckCircle';
88
import { faPlay } from '@fortawesome/free-solid-svg-icons/faPlay';
99

1010
import { validateButtonActionWithCompleteSection } from 'evolution-frontend/lib/services/display/frontendHelper';
11-
import { getSwitchPersonWidgets } from 'evolution-common/lib/services/questionnaire/sections/common/widgetsSwitchPerson';
1211
import { getButtonValidateAndGotoNextSection } from 'evolution-common/lib/services/questionnaire/sections/common/buttonValidateAndGotoNextSection';
1312

1413
import * as homeWidgets from './widgets/home';
@@ -45,9 +44,6 @@ export const personDidTrips: any = householdMembersWidget
4544
export const buttonSaveNextSectionHouseholdMembers: any = householdMembersWidgets.buttonSaveNextSectionHouseholdMembers;
4645
export const selectPerson: any = householdMembersWidgets.selectPerson;
4746
export const buttonSelectPersonConfirm: any = householdMembersWidgets.buttonSelectPersonConfirm;
48-
const switchPersonsWidget = getSwitchPersonWidgets();
49-
export const activePersonTitle: any = switchPersonsWidget.activePersonTitle;
50-
export const buttonSwitchPerson: any = switchPersonsWidget.buttonSwitchPerson;
5147

5248
import * as profileWidgets from './widgets/profile';
5349

@@ -101,14 +97,6 @@ export const buttonVisitedPlacesConfirmNextSection: any = visitedPlacesWidgets.b
10197

10298
import * as segmentsWidgets from './widgets/segments';
10399

104-
export const personTrips: any = segmentsWidgets.personTrips;
105-
export const personTripsTitle: any = segmentsWidgets.personTripsTitle;
106-
export const segments: any = segmentsWidgets.segments;
107-
export const segmentIntro: any = segmentsWidgets.segmentIntro;
108-
export const segmentSameModeAsReverseTrip: any = segmentsWidgets.segmentSameModeAsReverseTrip;
109-
export const segmentModePre: any = segmentsWidgets.segmentModePre;
110-
export const segmentMode: any = segmentsWidgets.segmentMode;
111-
export const segmentHasNextMode: any = segmentsWidgets.segmentHasNextMode;
112100
export const segmentParkingType: any = segmentsWidgets.segmentParkingType;
113101
export const segmentParkingPaymentType: any = segmentsWidgets.segmentParkingPaymentType;
114102
export const segmentVehicleOccupancy: any = segmentsWidgets.segmentVehicleOccupancy;
@@ -124,8 +112,6 @@ export const segmentTrainStationStart: any = segmentsWidgets.segmentTrainSt
124112
export const segmentTrainStationEnd: any = segmentsWidgets.segmentTrainStationEnd;
125113
export const segmentBusLines: any = segmentsWidgets.segmentBusLines;
126114
export const tripJunctionGeography: any = segmentsWidgets.tripJunctionGeography;
127-
export const introButtonSaveTrip: any = segmentsWidgets.introButtonSaveTrip;
128-
export const buttonSaveTrip: any = segmentsWidgets.buttonSaveTrip;
129115

130116
import * as travelBehaviorWidgets from './widgets/travelBehavior';
131117

example/demo_survey/src/survey/widgets/segments.tsx

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,8 @@ import subwayStations from '../subwayStations.json';
1818
import trainStations from '../trainStations.json';
1919
import busRoutes from '../busRoutes.json';
2020
import { GroupConfig } from 'evolution-common/lib/services/questionnaire/types';
21-
import { getModePreWidgetConfig } from 'evolution-common/lib/services/questionnaire/sections/segments/widgetSegmentModePre';
22-
import { getModeWidgetConfig } from 'evolution-common/lib/services/questionnaire/sections/segments/widgetSegmentMode';
23-
import { getSameAsReverseTripWidgetConfig } from 'evolution-common/lib/services/questionnaire/sections/segments/widgetSameAsReverseTrip';
24-
import { getSegmentHasNextModeWidgetConfig } from 'evolution-common/lib/services/questionnaire/sections/segments/widgetSegmentHasNextMode';
25-
import { getSegmentsGroupConfig } from 'evolution-common/lib/services/questionnaire/sections/segments/groupSegments';
26-
import { getTripSegmentsIntro } from 'evolution-common/lib/services/questionnaire/sections/segments/widgetTripSegmentsIntro';
27-
import { getButtonSaveTripSegmentsConfig } from 'evolution-common/lib/services/questionnaire/sections/segments/buttonSaveTripSegments';
28-
import { getPersonsTripsGroupConfig } from 'evolution-common/lib/services/questionnaire/sections/segments/groupPersonTrips';
29-
import { getPersonsTripsTitleWidgetConfig } from 'evolution-common/lib/services/questionnaire/sections/segments/widgetPersonTripsTitle';
3021
import { getFormattedDate, validateButtonAction } from 'evolution-frontend/lib/services/display/frontendHelper';
3122

32-
export const personTrips: GroupConfig = getPersonsTripsGroupConfig();
33-
34-
export const segments: GroupConfig = getSegmentsGroupConfig();
3523
/*
3624
TODO These were the original widgets for the group, as well as some from other surveys, that should eventually be configurable
3725
widgets: [
@@ -61,17 +49,6 @@ widgets: [
6149
]
6250
*/
6351

64-
export const segmentIntro = getTripSegmentsIntro();;
65-
66-
export const segmentSameModeAsReverseTrip = getSameAsReverseTripWidgetConfig();
67-
68-
// FIXME ModePre and Mode widgets should be automatically created if the section config enables them
69-
export const segmentModePre = getModePreWidgetConfig({ segmentConfig: segmentSectionConfig });
70-
71-
export const segmentMode = getModeWidgetConfig({ segmentConfig: segmentSectionConfig });
72-
73-
export const segmentHasNextMode = getSegmentHasNextModeWidgetConfig();
74-
7552
export const segmentVehicleOccupancy = {
7653
type: "question",
7754
path: "vehicleOccupancy",
@@ -2459,8 +2436,6 @@ export const segmentParkingPaymentType = {
24592436
}
24602437
};
24612438

2462-
export const personTripsTitle = getPersonsTripsTitleWidgetConfig({ getFormattedDate })
2463-
24642439
export const introButtonSaveTrip = {
24652440
type: "text",
24662441
containsHtml: true,
@@ -2470,9 +2445,3 @@ export const introButtonSaveTrip = {
24702445
en: `<p class="no-bottom-margin center _oblique">If you selected <strong>all</strong> modes of transport used during this trip:</p>`
24712446
}
24722447
};
2473-
2474-
// FIXME The options should be in a config somewhere for frontend mapping
2475-
export const buttonSaveTrip = getButtonSaveTripSegmentsConfig({
2476-
iconMapper: { 'check-circle': faCheckCircle },
2477-
buttonActions: { validateButtonAction: validateButtonAction },
2478-
});

packages/evolution-common/src/services/questionnaire/sections/common/__tests__/widgetsSwitchPerson.test.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,18 @@
66
*/
77
import _cloneDeep from 'lodash/cloneDeep';
88

9-
import { getSwitchPersonWidgets } from '../widgetsSwitchPerson';
9+
import { SwitchPersonWidgetsFactory } from '../widgetsSwitchPerson';
1010
import { interviewAttributesForTestCases } from '../../../../../tests/surveys';
1111
import * as utilHelpers from '../../../../../utils/helpers';
12-
import { t } from 'i18next';
1312
import { ButtonWidgetConfig, TextWidgetConfig } from '../../../../questionnaire/types';
1413

1514
import * as odHelpers from '../../../../odSurvey/helpers';
16-
import { start } from 'repl';
15+
16+
const widgetFactoryOptions = {
17+
getFormattedDate: (date: string) => date,
18+
buttonActions: { validateButtonAction: jest.fn() },
19+
iconMapper: {}
20+
};
1721

1822
jest.mock('../../../../odSurvey/helpers', () => ({
1923
getInterviewablePersonsArray: jest.fn().mockReturnValue([]),
@@ -28,10 +32,10 @@ beforeEach(() => {
2832
jest.clearAllMocks();
2933
});
3034

31-
describe('getSwitchPersonWidgets', () => {
35+
describe('SwitchPersonWidgets', () => {
3236

3337
test('should return the correct widget configs', () => {
34-
const widgetConfig = getSwitchPersonWidgets();
38+
const widgetConfig = new SwitchPersonWidgetsFactory(widgetFactoryOptions).getWidgetConfigs();
3539
expect(widgetConfig).toEqual({
3640
activePersonTitle: {
3741
type: 'text',
@@ -66,7 +70,7 @@ describe('getSwitchPersonWidgets', () => {
6670

6771
describe('activePersonTitle widget', () => {
6872

69-
const widgetConfig = getSwitchPersonWidgets({})['activePersonTitle'] as TextWidgetConfig;
73+
const widgetConfig = new SwitchPersonWidgetsFactory(widgetFactoryOptions).getWidgetConfigs()['activePersonTitle'] as TextWidgetConfig;
7074

7175
describe('conditional', () => {
7276
const conditional = widgetConfig.conditional;
@@ -112,7 +116,7 @@ describe('activePersonTitle widget', () => {
112116
});
113117

114118
describe('buttonSwitchPerson widget', () => {
115-
const widgetConfig = getSwitchPersonWidgets({})['buttonSwitchPerson'] as ButtonWidgetConfig;
119+
const widgetConfig = new SwitchPersonWidgetsFactory(widgetFactoryOptions).getWidgetConfigs()['buttonSwitchPerson'] as ButtonWidgetConfig;
116120

117121
describe('conditional', () => {
118122

packages/evolution-common/src/services/questionnaire/sections/common/widgetsSwitchPerson.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
* License text available at https://opensource.org/licenses/MIT
66
*/
77

8-
import { ButtonWidgetConfig, TextWidgetConfig } from '../../../questionnaire/types';
8+
import { ButtonWidgetConfig, TextWidgetConfig, WidgetConfig } from '../../../questionnaire/types';
99
import * as odHelpers from '../../../odSurvey/helpers';
1010
import { TFunction } from 'i18next';
1111
import { InterviewUpdateCallbacks, UserInterviewAttributes } from '../../types';
12+
import { WidgetConfigFactory, WidgetFactoryOptions } from '../types';
1213

1314
const hasMoreThanOneInterviewablePerson = (interview: UserInterviewAttributes): boolean =>
1415
odHelpers.getInterviewablePersonsArray({ interview }).length > 1;
@@ -69,13 +70,13 @@ const buttonSwitchPerson: ButtonWidgetConfig = {
6970
* @param options
7071
* @returns The widget configurations to switch between persons during a survey
7172
*/
72-
export const getSwitchPersonWidgets = (
73-
// FIXME: Type this when there is a few more widgets implemented
74-
_options: { context?: () => string } = {}
75-
): { activePersonTitle: TextWidgetConfig; buttonSwitchPerson: ButtonWidgetConfig } => {
76-
// TODO These should be some configuration receive here to fine-tune the section's content
77-
return {
73+
export class SwitchPersonWidgetsFactory implements WidgetConfigFactory {
74+
constructor(private options: WidgetFactoryOptions) {
75+
/** Nothing to do */
76+
}
77+
78+
getWidgetConfigs = (): Record<string, WidgetConfig> => ({
7879
activePersonTitle,
7980
buttonSwitchPerson
80-
};
81-
};
81+
});
82+
}

0 commit comments

Comments
 (0)