Skip to content

Commit

Permalink
Merge pull request #1100 from JGreenlee/bulk-logs-nov2023
Browse files Browse the repository at this point in the history
🪵 Add more logs and error boundaries in Label and Dashboard
  • Loading branch information
shankari authored Nov 9, 2023
2 parents b9f1483 + 98a9db6 commit edafd98
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 101 deletions.
5 changes: 5 additions & 0 deletions www/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -398,9 +398,14 @@
"errors": {
"registration-check-token": "User registration error. Please check your token and try again.",
"not-registered-cant-contact": "User is not registered, so the server cannot be contacted.",
"while-initializing-label": "While initializing Label tab: ",
"while-populating-composite": "Error while populating composite trips",
"while-loading-another-week": "Error while loading travel of {{when}} week",
"while-loading-specific-week": "Error while loading travel for the week of {{day}}",
"while-updating-timeline": "While updating timeline: ",
"while-refreshing-label": "While refreshing Label tab: ",
"while-repopulating-entry": "While repopulating timeline entry: ",
"while-loading-metrics": "While loading metrics: ",
"while-log-messages": "While getting messages from the log ",
"while-max-index": "While getting max index "
},
Expand Down
7 changes: 4 additions & 3 deletions www/js/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import { setServerConnSettings } from './config/serverConn';
import AppStatusModal from './control/AppStatusModal';
import usePermissionStatus from './usePermissionStatus';
import { withErrorBoundary } from './plugin/ErrorBoundary';

const defaultRoutes = (t) => [
{
Expand Down Expand Up @@ -55,9 +56,9 @@ const App = () => {
}, [appConfig, t]);

const renderScene = BottomNavigation.SceneMap({
label: LabelTab,
metrics: MetricsTab,
control: ProfileSettings,
label: withErrorBoundary(LabelTab),
metrics: withErrorBoundary(MetricsTab),
control: withErrorBoundary(ProfileSettings),
});

const refreshOnboardingState = () => getPendingOnboardingState().then(setOnboardingState);
Expand Down
8 changes: 7 additions & 1 deletion www/js/components/Chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Chart as ChartJS, registerables } from 'chart.js';
import { Chart as ChartJSChart } from 'react-chartjs-2';
import Annotation, { AnnotationOptions, LabelPosition } from 'chartjs-plugin-annotation';
import { dedupColors, getChartHeight, darkenOrLighten } from './charting';
import { logDebug } from '../plugin/logger';

ChartJS.register(...registerables, Annotation);

Expand Down Expand Up @@ -134,6 +135,9 @@ const Chart = ({
? {}
: {
callback: (value, i) => {
logDebug(`Horizontal axis callback: i = ${i};
chartDatasets = ${JSON.stringify(chartDatasets)};
chartDatasets[0].data = ${JSON.stringify(chartDatasets[0].data)}`);
const label = chartDatasets[0].data[i].y;
if (typeof label == 'string' && label.includes('\n'))
return label.split('\n');
Expand Down Expand Up @@ -168,7 +172,9 @@ const Chart = ({
? {}
: {
callback: (value, i) => {
console.log('testing vertical', chartData, i);
logDebug(`Vertical axis callback: i = ${i};
chartDatasets = ${JSON.stringify(chartDatasets)};
chartDatasets[0].data = ${JSON.stringify(chartDatasets[0].data)}`);
const label = chartDatasets[0].data[i].x;
if (typeof label == 'string' && label.includes('\n'))
return label.split('\n');
Expand Down
201 changes: 115 additions & 86 deletions www/js/diary/LabelTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
import { fillLocationNamesOfTrip, resetNominatimLimiter } from './addressNamesHelper';
import { SurveyOptions } from '../survey/survey';
import { getLabelOptions } from '../survey/multilabel/confirmHelper';
import { displayError } from '../plugin/logger';
import { displayError, displayErrorMsg, logDebug, logWarn } from '../plugin/logger';
import { useTheme } from 'react-native-paper';
import { getPipelineRangeTs } from '../commHelper';

Expand Down Expand Up @@ -58,55 +58,66 @@ const LabelTab = () => {

// initialization, once the appConfig is loaded
useEffect(() => {
if (!appConfig) return;
const surveyOptKey = appConfig.survey_info['trip-labels'];
const surveyOpt = SurveyOptions[surveyOptKey];
setSurveyOpt(surveyOpt);
showPlaces = appConfig.survey_info?.buttons?.['place-notes'];
getLabelOptions(appConfig).then((labelOptions) => setLabelOptions(labelOptions));
labelPopulateFactory = getAngularService(surveyOpt.service);
const tripSurveyName = appConfig.survey_info?.buttons?.['trip-notes']?.surveyName;
const placeSurveyName = appConfig.survey_info?.buttons?.['place-notes']?.surveyName;
enbs.initConfig(tripSurveyName, placeSurveyName);
try {
if (!appConfig) return;
const surveyOptKey = appConfig.survey_info['trip-labels'];
const surveyOpt = SurveyOptions[surveyOptKey];
setSurveyOpt(surveyOpt);
showPlaces = appConfig.survey_info?.buttons?.['place-notes'];
getLabelOptions(appConfig).then((labelOptions) => setLabelOptions(labelOptions));
labelPopulateFactory = getAngularService(surveyOpt.service);
const tripSurveyName = appConfig.survey_info?.buttons?.['trip-notes']?.surveyName;
const placeSurveyName = appConfig.survey_info?.buttons?.['place-notes']?.surveyName;
enbs.initConfig(tripSurveyName, placeSurveyName);

// we will show filters if 'additions' are not configured
// https://github.com/e-mission/e-mission-docs/issues/894
if (appConfig.survey_info?.buttons == undefined) {
// initalize filters
const tripFilter = surveyOpt.filter;
const allFalseFilters = tripFilter.map((f, i) => ({
...f,
state: i == 0 ? true : false, // only the first filter will have state true on init
}));
setFilterInputs(allFalseFilters);
// we will show filters if 'additions' are not configured
// https://github.com/e-mission/e-mission-docs/issues/894
if (appConfig.survey_info?.buttons == undefined) {
// initalize filters
const tripFilter = surveyOpt.filter;
const allFalseFilters = tripFilter.map((f, i) => ({
...f,
state: i == 0 ? true : false, // only the first filter will have state true on init
}));
setFilterInputs(allFalseFilters);
}
loadTimelineEntries();
} catch (e) {
displayError(e, t('errors.while-initializing-label'));
}
loadTimelineEntries();
}, [appConfig, refreshTime]);

// whenever timelineMap is updated, update the displayedEntries
// according to the active filter
useEffect(() => {
if (!timelineMap) return setDisplayedEntries(null);
const allEntries = Array.from<any>(timelineMap.values());
const activeFilter = filterInputs?.find((f) => f.state == true);
let entriesToDisplay = allEntries;
if (activeFilter) {
const entriesAfterFilter = allEntries.filter(
(t) => t.justRepopulated || activeFilter?.filter(t),
);
/* next, filter out any untracked time if the trips that came before and
try {
if (!timelineMap) return setDisplayedEntries(null);
const allEntries = Array.from<any>(timelineMap.values());
const activeFilter = filterInputs?.find((f) => f.state == true);
let entriesToDisplay = allEntries;
if (activeFilter) {
const entriesAfterFilter = allEntries.filter(
(t) => t.justRepopulated || activeFilter?.filter(t),
);
/* next, filter out any untracked time if the trips that came before and
after it are no longer displayed */
entriesToDisplay = entriesAfterFilter.filter((tlEntry) => {
if (!tlEntry.origin_key.includes('untracked')) return true;
const prevTrip = allEntries[allEntries.indexOf(tlEntry) - 1];
const nextTrip = allEntries[allEntries.indexOf(tlEntry) + 1];
const prevTripDisplayed = entriesAfterFilter.includes(prevTrip);
const nextTripDisplayed = entriesAfterFilter.includes(nextTrip);
// if either the trip before or after is displayed, then keep the untracked time
return prevTripDisplayed || nextTripDisplayed;
});
entriesToDisplay = entriesAfterFilter.filter((tlEntry) => {
if (!tlEntry.origin_key.includes('untracked')) return true;
const prevTrip = allEntries[allEntries.indexOf(tlEntry) - 1];
const nextTrip = allEntries[allEntries.indexOf(tlEntry) + 1];
const prevTripDisplayed = entriesAfterFilter.includes(prevTrip);
const nextTripDisplayed = entriesAfterFilter.includes(nextTrip);
// if either the trip before or after is displayed, then keep the untracked time
return prevTripDisplayed || nextTripDisplayed;
});
logDebug('After filtering, entriesToDisplay = ' + JSON.stringify(entriesToDisplay));
} else {
logDebug('No active filter, displaying all entries');
}
setDisplayedEntries(entriesToDisplay);
} catch (e) {
displayError(e, t('errors.while-updating-timeline'));
}
setDisplayedEntries(entriesToDisplay);
}, [timelineMap, filterInputs]);

async function loadTimelineEntries() {
Expand All @@ -117,15 +128,12 @@ const LabelTab = () => {
labelPopulateFactory,
enbs,
);
Logger.log(
'After reading unprocessedInputs, labelsResultMap =' +
JSON.stringify(labelsResultMap) +
'; notesResultMap = ' +
JSON.stringify(notesResultMap),
);
logDebug(`LabelTab: After reading unprocessedInputs,
labelsResultMap = ${JSON.stringify(labelsResultMap)};
notesResultMap = ${JSON.stringify(notesResultMap)}`);
setPipelineRange(pipelineRange);
} catch (error) {
Logger.displayError('Error while loading pipeline range', error);
} catch (e) {
displayError(e, 'Error while loading pipeline range');
setIsLoading(false);
}
}
Expand All @@ -138,15 +146,22 @@ const LabelTab = () => {
}, [pipelineRange]);

function refresh() {
setIsLoading('replace');
resetNominatimLimiter();
setQueriedRange(null);
setTimelineMap(null);
setRefreshTime(new Date());
try {
logDebug('Refreshing LabelTab');
setIsLoading('replace');
resetNominatimLimiter();
setQueriedRange(null);
setTimelineMap(null);
setRefreshTime(new Date());
} catch (e) {
displayError(e, t('errors.while-refreshing-label'));
}
}

async function loadAnotherWeek(when: 'past' | 'future') {
try {
logDebug('LabelTab: loadAnotherWeek into the ' + when);

const reachedPipelineStart =
queriedRange?.start_ts && queriedRange.start_ts <= pipelineRange.start_ts;
const reachedPipelineEnd =
Expand Down Expand Up @@ -183,6 +198,7 @@ const LabelTab = () => {

async function loadSpecificWeek(day: string) {
try {
logDebug('LabelTab: loadSpecificWeek for day ' + day);
if (!isLoading) setIsLoading('replace');
resetNominatimLimiter();
const threeDaysBefore = moment(day).subtract(3, 'days').unix();
Expand All @@ -197,6 +213,11 @@ const LabelTab = () => {
}

function handleFetchedTrips(ctList, utList, mode: 'prepend' | 'append' | 'replace') {
logDebug(`LabelTab: handleFetchedTrips with
mode = ${mode};
ctList = ${JSON.stringify(ctList)};
utList = ${JSON.stringify(utList)}`);

const tripsRead = ctList.concat(utList);
populateCompositeTrips(
tripsRead,
Expand All @@ -214,23 +235,22 @@ const LabelTab = () => {
fillLocationNamesOfTrip(trip);
});
const readTimelineMap = compositeTrips2TimelineMap(tripsRead, showPlaces);
logDebug(`LabelTab: after composite trips converted,
readTimelineMap = ${JSON.stringify(readTimelineMap)}`);
if (mode == 'append') {
setTimelineMap(new Map([...timelineMap, ...readTimelineMap]));
} else if (mode == 'prepend') {
setTimelineMap(new Map([...readTimelineMap, ...timelineMap]));
} else if (mode == 'replace') {
setTimelineMap(readTimelineMap);
} else {
return console.error('Unknown insertion mode ' + mode);
return displayErrorMsg('Unknown insertion mode ' + mode);
}
}

async function fetchTripsInRange(startTs: number, endTs: number) {
if (!pipelineRange.start_ts) {
console.warn('trying to read data too early, early return');
return;
}

if (!pipelineRange.start_ts) return logWarn('No pipelineRange yet - early return');
logDebug('LabelTab: fetchTripsInRange from ' + startTs + ' to ' + endTs);
const readCompositePromise = Timeline.readAllCompositeTrips(startTs, endTs);
let readUnprocessedPromise;
if (endTs >= pipelineRange.end_ts) {
Expand All @@ -249,6 +269,8 @@ const LabelTab = () => {
readUnprocessedPromise = Promise.resolve([]);
}
const results = await Promise.all([readCompositePromise, readUnprocessedPromise]);
logDebug(`LabelTab: readCompositePromise resolved as: ${JSON.stringify(results[0])};
readUnprocessedPromise resolved as: ${JSON.stringify(results[1])}`);
return results;
}

Expand All @@ -260,34 +282,41 @@ const LabelTab = () => {

const timelineMapRef = useRef(timelineMap);
async function repopulateTimelineEntry(oid: string) {
if (!timelineMap.has(oid))
return console.error('Item with oid: ' + oid + ' not found in timeline');
const [newLabels, newNotes] = await getLocalUnprocessedInputs(
pipelineRange,
labelPopulateFactory,
enbs,
);
const repopTime = new Date().getTime();
const newEntry = { ...timelineMap.get(oid), justRepopulated: repopTime };
labelPopulateFactory.populateInputsAndInferences(newEntry, newLabels);
enbs.populateInputsAndInferences(newEntry, newNotes);
const newTimelineMap = new Map(timelineMap).set(oid, newEntry);
setTimelineMap(newTimelineMap);
try {
logDebug('LabelTab: Repopulating timeline entry with oid ' + oid);
if (!timelineMap.has(oid))
return displayErrorMsg('Item with oid: ' + oid + ' not found in timeline');
const [newLabels, newNotes] = await getLocalUnprocessedInputs(
pipelineRange,
labelPopulateFactory,
enbs,
);
const repopTime = new Date().getTime();
logDebug('LabelTab: creating new entry for oid ' + oid + ' with repopTime ' + repopTime);
const newEntry = { ...timelineMap.get(oid), justRepopulated: repopTime };
labelPopulateFactory.populateInputsAndInferences(newEntry, newLabels);
enbs.populateInputsAndInferences(newEntry, newNotes);
logDebug('LabelTab: after repopulating, newEntry = ' + JSON.stringify(newEntry));
const newTimelineMap = new Map(timelineMap).set(oid, newEntry);
setTimelineMap(newTimelineMap);

// after 30 seconds, remove the justRepopulated flag unless it was repopulated again since then
/* ref is needed to avoid stale closure:
// after 30 seconds, remove the justRepopulated flag unless it was repopulated again since then
/* ref is needed to avoid stale closure:
https://legacy.reactjs.org/docs/hooks-faq.html#why-am-i-seeing-stale-props-or-state-inside-my-function */
timelineMapRef.current = newTimelineMap;
setTimeout(() => {
const entry = { ...timelineMapRef.current.get(oid) };
if (entry.justRepopulated != repopTime)
return console.log('Entry ' + oid + ' was repopulated again, skipping');
const newTimelineMap = new Map(timelineMapRef.current).set(oid, {
...entry,
justRepopulated: false,
});
setTimelineMap(newTimelineMap);
}, 30000);
timelineMapRef.current = newTimelineMap;
setTimeout(() => {
const entry = { ...timelineMapRef.current.get(oid) };
if (entry.justRepopulated != repopTime)
return logDebug('Entry ' + oid + ' was repopulated again, skipping');
const newTimelineMap = new Map(timelineMapRef.current).set(oid, {
...entry,
justRepopulated: false,
});
setTimelineMap(newTimelineMap);
}, 30000);
} catch (e) {
displayError(e, t('errors.while-repopulating-entry'));
}
}

const contextVals = {
Expand Down
30 changes: 19 additions & 11 deletions www/js/metrics/MetricsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import DailyActiveMinutesCard from './DailyActiveMinutesCard';
import CarbonTextCard from './CarbonTextCard';
import ActiveMinutesTableCard from './ActiveMinutesTableCard';
import { getAggregateData, getMetrics } from '../commHelper';
import { displayError, logDebug } from '../plugin/logger';

export const METRIC_LIST = ['duration', 'mean_speed', 'count', 'distance'] as const;

Expand Down Expand Up @@ -54,17 +55,24 @@ const MetricsTab = () => {
}, [dateRange]);

async function loadMetricsForPopulation(population: 'user' | 'aggregate', dateRange: DateTime[]) {
const serverResponse = await fetchMetricsFromServer(population, dateRange);
console.debug('Got metrics = ', serverResponse);
const metrics = {};
const dataKey = population == 'user' ? 'user_metrics' : 'aggregate_metrics';
METRIC_LIST.forEach((metricName, i) => {
metrics[metricName] = serverResponse[dataKey][i];
});
if (population == 'user') {
setUserMetrics(metrics as MetricsData);
} else {
setAggMetrics(metrics as MetricsData);
try {
logDebug(`MetricsTab: fetching metrics for population ${population}'
in date range ${JSON.stringify(dateRange)}`);
const serverResponse = await fetchMetricsFromServer(population, dateRange);
logDebug('MetricsTab: received metrics: ' + JSON.stringify(serverResponse));
const metrics = {};
const dataKey = population == 'user' ? 'user_metrics' : 'aggregate_metrics';
METRIC_LIST.forEach((metricName, i) => {
metrics[metricName] = serverResponse[dataKey][i];
});
logDebug('MetricsTab: parsed metrics: ' + JSON.stringify(metrics));
if (population == 'user') {
setUserMetrics(metrics as MetricsData);
} else {
setAggMetrics(metrics as MetricsData);
}
} catch (e) {
displayError(e, t('errors.while-loading-metrics'));
}
}

Expand Down
Loading

0 comments on commit edafd98

Please sign in to comment.