Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the Dashboard tab configurable, and implement new components #1142

Merged
merged 60 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from 55 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
946758a
show metrics tab if phone_dashboard_ui is set in app config
JGreenlee Apr 3, 2024
d3034eb
show/hide sections in MetricsTab based on config
JGreenlee Apr 3, 2024
5130653
show/hide unlabeled metrics based on config
jiji14 Apr 4, 2024
16edaf2
set the active modes based on config (active_travel_options.modes_list)
jiji14 Apr 5, 2024
b17ffb7
set summary list based on config (summary_options.metrics_list)
jiji14 Apr 5, 2024
21adb20
Merge branch 'refactoring_timelinecontext' of https://github.com/JGre…
JGreenlee Apr 12, 2024
84f8a51
Merge remote-tracking branch 'other/refactoring_timelinecontext' into…
jiji14 Apr 18, 2024
b95c268
show uncertain-footnote only when to show the uncertainty
jiji14 Apr 19, 2024
57d2298
rendering surveys Card components dynamically
jiji14 Apr 19, 2024
18e99bb
Surveys Cards components UI done
jiji14 Apr 19, 2024
2281c7e
Chart UI fix
jiji14 Apr 25, 2024
c38c353
update TimelineScrollList only when the active tab is 'label'
jiji14 Apr 26, 2024
1f7a692
add getSurveyMetric API endpoint
jiji14 May 4, 2024
58cfb5f
get new survey data every 24 hours
jiji14 May 4, 2024
8150e95
delete dummy data and process survey data from server
jiji14 May 5, 2024
ed52e0f
Merge branch 'refactoring_timelinecontext' of https://github.com/JGre…
JGreenlee May 13, 2024
d68354e
Delete the getSurveyMetric API call from the server logic alteration
jiji14 May 16, 2024
d4b3f51
Separate 'Survey comparison Card' and 'Leaderboard Card'
jiji14 May 16, 2024
abec657
use the new yyyy_mm_dd endpoint for agg metrics
JGreenlee May 20, 2024
5ab984c
refactor valueForModeOnDay -> valueForFieldOnDay
JGreenlee May 20, 2024
5916fab
display response_count MetricsCard properly
JGreenlee May 20, 2024
ab6fece
include primary_ble_sensed_mode in derived properties
JGreenlee May 20, 2024
c9628e0
support localhost metrics when appConfig does not have 'server'
JGreenlee May 20, 2024
32336ea
add 'metrics.responses' to en.json
JGreenlee May 20, 2024
1d99f59
use real data for SurveyComparisonCard
JGreenlee May 21, 2024
c7fbc25
use real data for SurveyTripCategoriesCard
JGreenlee May 21, 2024
5a8759e
comment out leaderboard; remove dummy data
JGreenlee May 21, 2024
269faed
"metrics list" -> "metric list"
JGreenlee May 21, 2024
cc4d134
show "No data" better when there's nothing to show
JGreenlee May 21, 2024
7e1c536
use e-mission-common @ 0.5.0
JGreenlee May 21, 2024
7510bf1
use semver for e-mission-common dependency
JGreenlee May 23, 2024
2e02fb7
Merge branch 'master' of https://github.com/e-mission/e-mission-phone…
JGreenlee May 23, 2024
f28bc23
fix filters issue on 'additions' configs
JGreenlee May 23, 2024
18089bf
on config refresh, wait for resources to finish caching
JGreenlee May 23, 2024
0642f06
remove response_count from default metric_list
JGreenlee May 23, 2024
e4ba6d0
fix metrics values summing
JGreenlee May 23, 2024
6ff253f
refactor metrics "units" & "format" fns to support response_count charts
JGreenlee May 24, 2024
34a99da
fix incorrect summing of response_count objs
JGreenlee May 24, 2024
f71280f
unify datepickers, loadSpecificWeek -> loadDateRange; simplify
JGreenlee May 24, 2024
637d496
refactor & simplify MetricsTab
JGreenlee May 24, 2024
66bbc62
update metricsTypes
JGreenlee May 24, 2024
6ef2adf
remove dateForDayOfMetricData function
JGreenlee May 24, 2024
bbfbfa1
have MetricValue support type generics to disambiguate number vs object
JGreenlee May 24, 2024
96549b3
use e-mission-common @ 0.5.1
JGreenlee May 24, 2024
114d15e
improve MetricsTab loading
JGreenlee May 24, 2024
cbefbbd
remove unused code from MetricsTab
JGreenlee May 24, 2024
c803973
humanize all caps values for Metrics Tab
JGreenlee May 24, 2024
b3e80df
fix date range picker timezone issue
JGreenlee May 24, 2024
663f86c
update metricsHelper.test.ts to use the unified DayOfMetricData type
JGreenlee May 24, 2024
90b631c
Hide chart when there is no survey data
jiji14 May 25, 2024
a3570d6
type error handling
jiji14 May 25, 2024
084224e
fix checking data logic for surveyComparisonCard
jiji14 May 25, 2024
8cf5fb0
Add maxBarThickness for chart.js
jiji14 May 25, 2024
f55faf1
add more unit tests
jiji14 May 28, 2024
abf2f3f
export functions for tests and fix syntax issue
jiji14 May 28, 2024
66464ca
done unit tests with metricsHelper
jiji14 May 29, 2024
a963381
add useImperialConfig test
jiji14 May 29, 2024
3f08b6c
add getTheme unit test
jiji14 May 29, 2024
1bd1f48
add unit test if Carousel renders children correctly
jiji14 May 29, 2024
e55e5ae
add DateSelect rendering unit test
jiji14 May 30, 2024
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
2 changes: 1 addition & 1 deletion package.cordovabuild.json
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@
"cordova-custom-config": "^5.1.1",
"cordova-plugin-ibeacon": "git+https://github.com/louisg1337/cordova-plugin-ibeacon.git",
"core-js": "^2.5.7",
"e-mission-common": "git+https://github.com/JGreenlee/e-mission-common.git#0.4.4",
"e-mission-common": "github:JGreenlee/e-mission-common#semver:0.5.1",
"enketo-core": "^6.1.7",
"enketo-transformer": "^4.0.0",
"fast-xml-parser": "^4.2.2",
Expand Down
2 changes: 1 addition & 1 deletion package.serve.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
"chartjs-adapter-luxon": "^1.3.1",
"chartjs-plugin-annotation": "^3.0.1",
"core-js": "^2.5.7",
"e-mission-common": "git+https://github.com/JGreenlee/e-mission-common.git#0.4.4",
"e-mission-common": "github:JGreenlee/e-mission-common#semver:0.5.1",
"enketo-core": "^6.1.7",
"enketo-transformer": "^4.0.0",
"fast-xml-parser": "^4.2.2",
Expand Down
235 changes: 173 additions & 62 deletions www/__tests__/metricsHelper.test.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,53 @@
import { DateTime } from 'luxon';
import {
calculatePercentChange,
formatDate,
formatDateRangeOfDays,
getLabelsForDay,
getUniqueLabelsForDays,
secondsToHours,
secondsToMinutes,
segmentDaysByWeeks,
metricToValue,
tsForDayOfMetricData,
valueForFieldOnDay,
generateSummaryFromData,
isCustomLabels,
isAllCustom,
isOnFoot,
} from '../js/metrics/metricsHelper';
import {
DayOfClientMetricData,
DayOfMetricData,
DayOfServerMetricData,
} from '../js/metrics/metricsTypes';
import { DayOfMetricData } from '../js/metrics/metricsTypes';

describe('metricsHelper', () => {
describe('getUniqueLabelsForDays', () => {
const days1 = [
{ label_a: 1, label_b: 2 },
{ label_c: 1, label_d: 3 },
] as any as DayOfServerMetricData[];
it("should return unique labels for days with 'label_*'", () => {
{ mode_confirm_a: 1, mode_confirm_b: 2 },
{ mode_confirm_b: 1, mode_confirm_c: 3 },
{ mode_confirm_c: 1, mode_confirm_d: 3 },
] as any as DayOfMetricData[];
it("should return unique labels for days with 'mode_confirm_*'", () => {
expect(getUniqueLabelsForDays(days1)).toEqual(['a', 'b', 'c', 'd']);
});

const days2 = [
{ mode_a: 1, mode_b: 2 },
{ mode_c: 1, mode_d: 3 },
] as any as DayOfClientMetricData[];
it("should return unique labels for days with 'mode_*'", () => {
expect(getUniqueLabelsForDays(days2)).toEqual(['a', 'b', 'c', 'd']);
});
Comment on lines -25 to -31
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jiji14 please replace this with a test that actually have duplicate labels (see my comment in #1138 (comment))

});

describe('getLabelsForDay', () => {
const day1 = { label_a: 1, label_b: 2 } as any as DayOfServerMetricData;
it("should return labels for a day with 'label_*'", () => {
const day1 = { mode_confirm_a: 1, mode_confirm_b: 2 } as any as DayOfMetricData;
it("should return labels for a day with 'mode_confirm_*'", () => {
expect(getLabelsForDay(day1)).toEqual(['a', 'b']);
});
});

const day2 = { mode_a: 1, mode_b: 2 } as any as DayOfClientMetricData;
it("should return labels for a day with 'mode_*'", () => {
expect(getLabelsForDay(day2)).toEqual(['a', 'b']);
describe('secondsToMinutes', () => {
it("should convert from seconds to minutes properly", () => {
expect(secondsToMinutes(360)).toEqual(6);
});
});

// secondsToMinutes

// secondsToHours
describe('secondsToHours', () => {
it("should convert from seconds to hours properly", () => {
expect(secondsToHours(3600)).toEqual(1);
});
});

describe('segmentDaysByWeeks', () => {
const days1 = [
Expand All @@ -55,7 +57,7 @@ describe('metricsHelper', () => {
{ date: '2021-01-08' },
{ date: '2021-01-09' },
{ date: '2021-01-10' },
] as any as DayOfClientMetricData[];
] as any as DayOfMetricData[];

it("should segment days with 'date' into weeks", () => {
expect(segmentDaysByWeeks(days1, '2021-01-10')).toEqual([
Expand All @@ -70,59 +72,168 @@ describe('metricsHelper', () => {
[{ date: '2021-01-01' }, { date: '2021-01-02' }],
]);
});

const days2 = [
{ fmt_time: '2021-01-01T00:00:00Z' },
{ fmt_time: '2021-01-02T00:00:00Z' },
{ fmt_time: '2021-01-04T00:00:00Z' },
{ fmt_time: '2021-01-08T00:00:00Z' },
{ fmt_time: '2021-01-09T00:00:00Z' },
{ fmt_time: '2021-01-10T00:00:00Z' },
] as any as DayOfServerMetricData[];
it("should segment days with 'fmt_time' into weeks", () => {
expect(segmentDaysByWeeks(days2, '2021-01-10')).toEqual([
// most recent week
[
{ fmt_time: '2021-01-04T00:00:00Z' },
{ fmt_time: '2021-01-08T00:00:00Z' },
{ fmt_time: '2021-01-09T00:00:00Z' },
{ fmt_time: '2021-01-10T00:00:00Z' },
],
// prior week
[{ fmt_time: '2021-01-01T00:00:00Z' }, { fmt_time: '2021-01-02T00:00:00Z' }],
]);
});
Comment on lines -73 to -94
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, please fix this test that was removed to get the tests to passs

});

describe('formatDate', () => {
const day1 = { date: '2021-01-01' } as any as DayOfClientMetricData;
const day1 = { date: '2021-01-01' } as any as DayOfMetricData;
it('should format date', () => {
expect(formatDate(day1)).toEqual('1/1');
});

const day2 = { fmt_time: '2021-01-01T00:00:00Z' } as any as DayOfServerMetricData;
it('should format date', () => {
expect(formatDate(day2)).toEqual('1/1');
});
});

describe('formatDateRangeOfDays', () => {
const days1 = [
{ date: '2021-01-01' },
{ date: '2021-01-02' },
{ date: '2021-01-04' },
] as any as DayOfClientMetricData[];
] as any as DayOfMetricData[];
it('should format date range for days with date', () => {
expect(formatDateRangeOfDays(days1)).toEqual('1/1 - 1/4');
});
});

describe('metricToValue', () => {
const metric = {
walking: 10,
nUsers: 5,
};
it('returns correct value for user population', () => {
const result = metricToValue('user', metric, 'walking');
expect(result).toBe(10);
});

it('returns correct value for aggregate population', () => {
const result = metricToValue('aggregate', metric, 'walking');
expect(result).toBe(2);
});
});

describe('isOnFoot', () => {
it('returns true for on foot mode', () => {
const result = isOnFoot('WALKING');
expect(result).toBe(true);
});

it('returns false for non on foot mode', () => {
const result = isOnFoot('DRIVING');
expect(result).toBe(false);
});
});

describe('calculatePercentChange', () => {
it('calculates percent change correctly for low and high values', () => {
const pastWeekRange = { low: 10, high: 30 };
const previousWeekRange = { low: 5, high: 10 };
const result = calculatePercentChange(pastWeekRange, previousWeekRange);
expect(result.low).toBe(100);
expect(result.high).toBe(200);
});
});

describe('tsForDayOfMetricData', () => {
const mockDay = {
date: '2024-05-28T12:00:00Z',
nUsers: 10,
};
let _datesTsCache;
beforeEach(() => {
_datesTsCache = {};
});

it('calculates timestamp for a given day', () => {
const expectedTimestamp = DateTime.fromISO(mockDay.date).toSeconds();
const result = tsForDayOfMetricData(mockDay);
expect(result).toBe(expectedTimestamp);
});

it('caches the timestamp for subsequent calls with the same day', () => {
const firstResult = tsForDayOfMetricData(mockDay);
const secondResult = tsForDayOfMetricData(mockDay);
expect(secondResult).toBe(firstResult);
});
});

describe('valueForFieldOnDay', () => {
const mockDay = {
date: '2024-05-28T12:00:00Z',
nUsers: 10,
field_key: 'example_value'
};

it('returns the value for a specified field and key', () => {
const result = valueForFieldOnDay(mockDay, 'field', 'key');
expect(result).toBe('example_value');
});
});

const days2 = [
{ fmt_time: '2021-01-01T00:00:00Z' },
{ fmt_time: '2021-01-02T00:00:00Z' },
{ fmt_time: '2021-01-04T00:00:00Z' },
] as any as DayOfServerMetricData[];
it('should format date range for days with fmt_time', () => {
expect(formatDateRangeOfDays(days2)).toEqual('1/1 - 1/4');
describe('generateSummaryFromData', () => {
const modeMap = [
{ key: 'mode1', values: [['value1', 10], ['value2', 20]] },
{ key: 'mode2', values: [['value3', 30], ['value4', 40]] },
];
it('returns summary with sum for non-speed metric', () => {
const metric = 'some_metric';
const expectedResult = [
{ key: 'mode1', values: 30 },
{ key: 'mode2', values: 70 },
];
const result = generateSummaryFromData(modeMap, metric);
expect(result).toEqual(expectedResult);
});

it('returns summary with average for speed metric', () => {
const metric = 'mean_speed';
const expectedResult = [
{ key: 'mode1', values: 15 },
{ key: 'mode2', values: 35 },
];
const result = generateSummaryFromData(modeMap, metric);
expect(result).toEqual(expectedResult);
});
});

describe('isCustomLabels', () => {
const modeMap = [
{ key: 'label_mode1', values: [['value1', 10], ['value2', 20]] },
{ key: 'label_mode2', values: [['value3', 30], ['value4', 40]] },
];

it('returns true for all custom labels', () => {
const result = isCustomLabels(modeMap);
expect(result).toBe(true);
});

it('returns true for all sensed labels', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for sensed labels, we expect isCustomLabels to return false. Right now, this is identical to the previous test.

const result = isCustomLabels(modeMap);
expect(result).toBe(true);
});

it('returns false for mixed custom and sensed labels', () => {
const result = isCustomLabels(modeMap);
expect(result).toBe(false);
});
});

describe('isAllCustom', () => {
it('returns true when all keys are custom', () => {
const isSensedKeys = [false, false, false];
const isCustomKeys = [true, true, true];
const result = isAllCustom(isSensedKeys, isCustomKeys);
expect(result).toBe(true);
});

it('returns false when all keys are sensed', () => {
const isSensedKeys = [true, true, true];
const isCustomKeys = [false, false, false];
const result = isAllCustom(isSensedKeys, isCustomKeys);
expect(result).toBe(false);
});

it('returns undefined for mixed custom and sensed keys', () => {
const isSensedKeys = [true, false, true];
const isCustomKeys = [false, true, false];
const result = isAllCustom(isSensedKeys, isCustomKeys);
expect(result).toBe(undefined);
});
});
});
24 changes: 21 additions & 3 deletions www/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,9 @@
"trips": "trips",
"hours": "hours",
"minutes": "minutes",
"custom": "Custom"
"responses": "responses",
"custom": "Custom",
"no-data": "No data"
},

"diary": {
Expand Down Expand Up @@ -192,8 +194,9 @@
"chart": "Chart",
"change-data": "Change dates:",
"distance": "Distance",
"trips": "Trips",
"count": "Trip Count",
"duration": "Duration",
"response_count": "Response Count",
"fav-mode": "My Favorite Mode",
"speed": "My Speed",
"footprint": "My Footprint",
Expand Down Expand Up @@ -222,7 +225,22 @@
"weekly-goal-footnote": "³Weekly goal based on CDC recommendation of 150 minutes of moderate activity per week.",
"labeled": "Labeled",
"unlabeled": "Unlabeled²",
"footprint-label": "Footprint (kg CO₂)"
"footprint-label": "Footprint (kg CO₂)",
"surveys": "Surveys",
"leaderboard": "Leaderboard",
"survey-response-rate": "Survey Response Rate (%)",
"survey-leaderboard-desc": "This data has been accumulated since ",
"comparison": "Comparison",
"you": "You",
"others": "Others in group",
"trip-categories": "Trip Categories",
"ev-roading-trip": "EV Roaming trip",
"ev-return-trip": "EV Return trip",
"gas-car-trip": "Gas Car trip",
"response": "Response",
"no-response": "No Response",
"you-are-in": "You're in",
"place": " place!"
},

"details": {
Expand Down
4 changes: 3 additions & 1 deletion www/js/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ const Main = () => {
const timelineContext = useTimelineContext();

const routes = useMemo(() => {
const showMetrics = appConfig?.survey_info?.['trip-labels'] == 'MULTILABEL';
const showMetrics =
appConfig?.metrics?.phone_dashboard_ui ||
appConfig?.survey_info?.['trip-labels'] == 'MULTILABEL';
return showMetrics ? defaultRoutes(t) : defaultRoutes(t).filter((r) => r.key != 'metrics');
}, [appConfig, t]);

Expand Down
Loading
Loading