Skip to content

Commit

Permalink
fix LabelTab filtering
Browse files Browse the repository at this point in the history
The existing implementation used pub/sub events to trigger when filtering should happen. The events were triggered from TimelineContext.
However, this doesn't listen / wait for filterInputs to be defined because filterInputs is inside LabelTab, not TimelineContext. Filtering wasn't working because filterInputs was still empty when the initial filtering occured and LabelTab had no control over when the event was triggered.

A better solution is to listen for changes inside LabelTab. Every time the filters, the loaded trips, or the labels change, filtering occurs. If a trip had a user input in the last 30s, we skip it; but schedule a re-filter when its "immunity" expires.

Now LabelTab is responsible for all the filtering. TimelineContext doesn't have to worry about what LabelTab (its child) does. This is a cleaner, "React-recommended" unidirectional pattern
  • Loading branch information
JGreenlee committed May 21, 2024
1 parent 0cc4a14 commit db893d5
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 34 deletions.
13 changes: 0 additions & 13 deletions www/js/TimelineContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import {
} from './diary/timelineHelper';
import { getPipelineRangeTs } from './services/commHelper';
import { getNotDeletedCandidates, mapInputsToTimelineEntries } from './survey/inputMatcher';
import { publish } from './customEventHandler';
import { EnketoUserInputEntry } from './survey/enketo/enketoHelper';
import { VehicleIdentity } from './types/appConfigTypes';
import { primarySectionForTrip } from './diary/diaryHelper';
Expand Down Expand Up @@ -140,10 +139,6 @@ export const useTimelineContext = (): ContextProps => {
);
setTimelineLabelMap(newTimelineLabelMap);
setTimelineNotesMap(newTimelineNotesMap);
publish('applyLabelTabFilters', {
timelineMap,
timelineLabelMap: newTimelineLabelMap,
});
setTimelineIsLoading(false);
}, [timelineMap]);

Expand Down Expand Up @@ -321,14 +316,6 @@ export const useTimelineContext = (): ContextProps => {
},
};
setTimelineLabelMap(newTimelineLabelMap);
setTimeout(
() =>
publish('applyLabelTabFilters', {
timelineMap,
timelineLabelMap: newTimelineLabelMap,
}),
30000,
); // wait 30s before reapplying filters
} else if (inputType == 'note') {
const notesForEntry = timelineNotesMap?.[oid] || [];
const newAddition = { data: userInput, metadata: { write_ts: nowTs } };
Expand Down
44 changes: 23 additions & 21 deletions www/js/diary/LabelTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ import { logDebug } from '../plugin/logger';
import { configuredFilters as multilabelConfiguredFilters } from '../survey/multilabel/infinite_scroll_filters';
import { configuredFilters as enketoConfiguredFilters } from '../survey/enketo/infinite_scroll_filters';
import { TimelineEntry, isTrip } from '../types/diaryTypes';
import TimelineContext, { LabelTabFilter, TimelineLabelMap } from '../TimelineContext';
import TimelineContext, { LabelTabFilter } from '../TimelineContext';
import { AppContext } from '../App';
import { subscribe } from '../customEventHandler';

type LabelContextProps = {
displayedEntries: TimelineEntry[] | null;
Expand All @@ -29,8 +28,9 @@ export const LabelTabContext = createContext<LabelContextProps>({} as LabelConte

const LabelTab = () => {
const { appConfig } = useContext(AppContext);
const { pipelineRange, timelineMap } = useContext(TimelineContext);
const { pipelineRange, timelineMap, timelineLabelMap } = useContext(TimelineContext);

const [filterRefreshTs, setFilterRefreshTs] = useState<number>(0); // used to force a refresh of the filters
const [filterInputs, setFilterInputs] = useState<LabelTabFilter[]>([]);
const [displayedEntries, setDisplayedEntries] = useState<TimelineEntry[] | null>(null);

Expand All @@ -43,17 +43,12 @@ const LabelTab = () => {
appConfig.survey_info?.['trip-labels'] == 'ENKETO'
? enketoConfiguredFilters
: multilabelConfiguredFilters;
const allFalseFilters = tripFilters.map((f, i) => ({
const filtersWithState = tripFilters.map((f, i) => ({
...f,
state: i == 0 ? true : false, // only the first filter will have state true on init
}));
setFilterInputs(allFalseFilters);
setFilterInputs(filtersWithState);
}

subscribe('applyLabelTabFilters', (e) => {
logDebug('applyLabelTabFilters event received, calling applyFilters');
applyFilters(e.detail.timelineMap, e.detail.timelineLabelMap || {});
});
}, [appConfig]);

useEffect(() => {
Expand All @@ -65,24 +60,31 @@ const LabelTab = () => {
});
}, [timelineMap]);

function applyFilters(timelineMap, labelMap: TimelineLabelMap) {
useEffect(() => {
if (!timelineMap || !timelineLabelMap || !filterInputs.length) return;
logDebug('Applying filters');
const allEntries: TimelineEntry[] = Array.from(timelineMap.values());
const activeFilter = filterInputs?.find((f) => f.state == true);
let entriesToDisplay = allEntries;
if (activeFilter) {
const cutoffTs = new Date().getTime() / 1000 - 30; // 30s ago, as epoch seconds
const nowTs = new Date().getTime() / 1000;
const entriesAfterFilter = allEntries.filter((e) => {
// if the entry has a recently recorded user input, it is immune to filtering
const labels = labelMap[e._id.$oid];
for (let labelValue of Object.values(labels || [])) {
logDebug(`LabelTab filtering: labelValue = ${JSON.stringify(labelValue)}`);
if (labelValue?.metadata?.write_ts || 0 > cutoffTs) {
logDebug('LabelTab filtering: entry has recent user input, keeping');
return true;
}
const labels = timelineLabelMap[e._id.$oid];
const mostRecentInputTs = Object.values(labels || []).reduce((acc, label) => {
if (label?.metadata?.write_ts && label.metadata.write_ts > acc)
return label.metadata.write_ts;
return acc;
}, 0);
const entryImmuneUntil = mostRecentInputTs + 30; // 30s after the most recent user input
if (entryImmuneUntil > nowTs) {
logDebug(`LabelTab filtering: entry still immune, skipping.
Re-applying filters at ${entryImmuneUntil}`);
setTimeout(() => setFilterRefreshTs(entryImmuneUntil), (entryImmuneUntil - nowTs) * 1000);
return true;
}
// otherwise, just apply the filter
return activeFilter?.filter(e, labelMap[e._id.$oid]);
return activeFilter?.filter(e, timelineLabelMap[e._id.$oid]);
});
/* next, filter out any untracked time if the trips that came before and
after it are no longer displayed */
Expand All @@ -100,7 +102,7 @@ const LabelTab = () => {
logDebug('No active filter, displaying all entries');
}
setDisplayedEntries(entriesToDisplay);
}
}, [timelineMap, filterInputs, timelineLabelMap, filterRefreshTs]);

// once pipelineRange is set, update all unprocessed inputs
useEffect(() => {
Expand Down

0 comments on commit db893d5

Please sign in to comment.