From f1b7c5a3dba3d68edee0852102a0c68bf2dbff6b Mon Sep 17 00:00:00 2001 From: Willian Viana Date: Mon, 24 Jun 2024 18:39:17 -0300 Subject: [PATCH 1/7] fix(data-selector): Change the type of yearDataByWeek to handle it as an array --- .../widgets/fires/fires-alerts/index.js | 38 +++++++++---------- .../widgets/fires/fires-alerts/selectors.js | 10 +++-- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/components/widgets/fires/fires-alerts/index.js b/components/widgets/fires/fires-alerts/index.js index 02eb9ad70a..cc1bcdc00f 100644 --- a/components/widgets/fires/fires-alerts/index.js +++ b/components/widgets/fires/fires-alerts/index.js @@ -327,26 +327,24 @@ const defaultConfig = { const maxYear = Math.max(...years); const latestDate = latest && latest.date; - return ( - { - alerts: data, - latest: latestDate, - options: { - compareYear: years - .filter((y) => y !== maxYear) - .map((y) => ({ - label: y, - value: y, - })), - }, - settings: { - startDateAbsolute: moment(latestDate) - .add(-3, 'month') - .format('YYYY-MM-DD'), - endDateAbsolute: latestDate, - }, - } || {} - ); + return { + alerts: data, + latest: latestDate, + options: { + compareYear: years + .filter((y) => y !== maxYear) + .map((y) => ({ + label: y, + value: y, + })), + }, + settings: { + startDateAbsolute: moment(latestDate) + .add(-3, 'month') + .format('YYYY-MM-DD'), + endDateAbsolute: latestDate, + }, + }; }) ); }, diff --git a/components/widgets/fires/fires-alerts/selectors.js b/components/widgets/fires/fires-alerts/selectors.js index 0f6387ce76..1661d7f592 100644 --- a/components/widgets/fires/fires-alerts/selectors.js +++ b/components/widgets/fires/fires-alerts/selectors.js @@ -7,6 +7,7 @@ import sortBy from 'lodash/sortBy'; import orderBy from 'lodash/orderBy'; import sumBy from 'lodash/sumBy'; import groupBy from 'lodash/groupBy'; +import toArray from 'lodash/toArray'; import max from 'lodash/max'; import min from 'lodash/min'; @@ -71,7 +72,7 @@ export const getData = createSelector( }; years.forEach((year) => { - const yearDataByWeek = groupBy(groupedByYear[year], 'week'); + const yearDataByWeek = toArray(groupBy(groupedByYear[year], 'week')); const lastWeekOfYearIso = moment(`${year}-12-31`).isoWeek(); const yearLength = { [year]: moment(`${year}-12-31`).isoWeek() }; @@ -86,7 +87,7 @@ export const getData = createSelector( } for (let i = 1; i <= yearLength[year]; i += 1) { - if (Object.keys(yearDataByWeek).length < i) { + if (yearDataByWeek.length < i) { return; } @@ -94,13 +95,13 @@ export const getData = createSelector( ? yearDataByWeek[i].length - 1 : 0; - let objectsReduced = { count: 0, week: i, year: parseInt(year, 10) }; + let objectsReduced = {}; for (let index = 0; index <= yearDataLength; index += 1) { if (yearDataByWeek[i]) { objectsReduced = Object.assign(objectsReduced, { ...yearDataByWeek[i][index], - count: objectsReduced.count + yearDataByWeek[i][index].count, + count: yearDataByWeek[i][index].count, }); } } @@ -193,6 +194,7 @@ export const parseData = createSelector( return d; }); + return parsedData; } ); From 8f092ed207bb91bec2be842e5209844203baabe1 Mon Sep 17 00:00:00 2001 From: Willian Viana Date: Wed, 10 Jul 2024 14:08:26 -0300 Subject: [PATCH 2/7] fix(alerts): add mocked alerts to filled alerts object with missed weeks --- .../widgets/fires/fires-alerts/selectors.js | 91 ++++++++++++++++++- 1 file changed, 88 insertions(+), 3 deletions(-) diff --git a/components/widgets/fires/fires-alerts/selectors.js b/components/widgets/fires/fires-alerts/selectors.js index 1661d7f592..6991f0bd30 100644 --- a/components/widgets/fires/fires-alerts/selectors.js +++ b/components/widgets/fires/fires-alerts/selectors.js @@ -1,5 +1,13 @@ /* eslint-disable prefer-destructuring */ import { createSelector, createStructuredSelector } from 'reselect'; +import { + isWithinInterval, + startOfWeek, + endOfWeek, + eachWeekOfInterval, + getYear, + getWeek, +} from 'date-fns'; import moment from 'moment'; import { formatNumber } from 'utils/format'; import isEmpty from 'lodash/isEmpty'; @@ -33,8 +41,85 @@ const getIndicator = (state) => state.indicator; const MINGAP = 4; +const generateYearWeekArray = (startDate, endDate) => { + const weeks = eachWeekOfInterval( + { + start: startOfWeek(startDate, { weekStartsOn: 1 }), + end: endOfWeek(endDate, { weekStartsOn: 1 }), + }, + { weekStartsOn: 1 } + ); + + return weeks.map((date) => ({ + year: getYear(date), + week: getWeek(date, { weekStartsOn: 1 }), + })); +}; + +const getAllAlerts = createSelector([getAlerts], (alerts) => { + if (!alerts) return null; + + const filler = { iso: alerts[0]?.iso, alert__count: 0 }; + const minYear = '2012'; + const maxYear = new Date(); + + const yearProperty = 'alert__year'; + const weekProperty = 'alert__week'; + + const start = new Date(minYear?.toString()); + const end = new Date(maxYear?.toString()); + const completeYearWeekArray = generateYearWeekArray(start, end); + + // Create a set of existing year-week combinations for quick lookup + const existingAlerts = new Set( + alerts.map((alert) => `${alert[yearProperty]}-${alert[weekProperty]}`) + ); + + // Iterate through the complete array and add missing values + const mockedAlerts = completeYearWeekArray.map((item) => { + const weekStartDate = startOfWeek( + new Date(item.year, 0, (item.week - 1) * 7), + { weekStartsOn: 1 } + ); + const weekEndDate = endOfWeek(new Date(item.year, 0, (item.week - 1) * 7), { + weekStartsOn: 1, + }); + + if ( + isWithinInterval(weekStartDate, { start, end }) || + isWithinInterval(weekEndDate, { start, end }) + ) { + const key = `${item.year}-${item.week}`; + + if (!existingAlerts.has(key)) { + const mockedAlert = { + [yearProperty]: item.year, + [weekProperty]: item.week, + ...filler, + }; + + return mockedAlert; + } + } + + return null; + }); + + const allAlerts = [...alerts, ...mockedAlerts.filter((item) => !!item)]; + + // Sort the array again by year and week properties to maintain order + allAlerts.sort((a, b) => { + if (a[yearProperty] === b[yearProperty]) { + return a[weekProperty] - b[weekProperty]; + } + return a[yearProperty] - b[yearProperty]; + }); + + return allAlerts; +}); + export const getData = createSelector( - [getAlerts, getLatest], + [getAllAlerts, getLatest], (data, latest) => { if (!data || isEmpty(data)) return null; @@ -272,7 +357,7 @@ export const parseConfig = createSelector( unit: ` ${dataset.toUpperCase()} alerts`, color: colors.main, unitFormat: (value) => - Number.isInteger(value) && formatNumber({ num: value, unit: ',' }), + Number.isInteger(value) ? formatNumber({ num: value, unit: ',' }) : 0, }, ]; @@ -291,7 +376,7 @@ export const parseConfig = createSelector( color: '#49b5e3', nullValue: 'No data available', unitFormat: (value) => - Number.isInteger(value) && formatNumber({ num: value, unit: ',' }), + Number.isInteger(value) ? formatNumber({ num: value, unit: ',' }) : 0, }); } From 783dbe8f24c0ef0513487ddf5f0950eb1b814e85 Mon Sep 17 00:00:00 2001 From: Willian Viana Date: Thu, 11 Jul 2024 14:24:39 -0300 Subject: [PATCH 3/7] fix(alerts): set count as 0 instead null/undefined when there's no alerts --- components/widgets/fires/fires-alerts/selectors.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/widgets/fires/fires-alerts/selectors.js b/components/widgets/fires/fires-alerts/selectors.js index 6991f0bd30..23c172eda7 100644 --- a/components/widgets/fires/fires-alerts/selectors.js +++ b/components/widgets/fires/fires-alerts/selectors.js @@ -125,7 +125,7 @@ export const getData = createSelector( const parsedData = data.map((d) => ({ ...d, - count: d.alert__count || d.area_ha, + count: d.alert__count || d.area_ha || 0, week: parseInt(d.alert__week, 10), year: parseInt(d.alert__year, 10), })); @@ -273,7 +273,7 @@ export const parseData = createSelector( return { ...d, compareYear, - compareCount: compareWeek ? compareWeek.count : null, + compareCount: compareWeek ? compareWeek.count : 0, }; } From 3cd0f05bcd3357730a6749d8a9b8b5e03e520966 Mon Sep 17 00:00:00 2001 From: Willian Viana Date: Thu, 11 Jul 2024 14:58:24 -0300 Subject: [PATCH 4/7] fix(alerts): add property month only for the first week of the month Recharts uses dataKey to set the unique value displayed at XAxis. We were using date for it but hiding most part to display only 12 dates Now we are using month and setting it as a unique value --- .../widgets/fires/fires-alerts/selectors.js | 6 ++-- components/widgets/utils/data.js | 32 ++++++++++++------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/components/widgets/fires/fires-alerts/selectors.js b/components/widgets/fires/fires-alerts/selectors.js index 23c172eda7..4f5f9ac710 100644 --- a/components/widgets/fires/fires-alerts/selectors.js +++ b/components/widgets/fires/fires-alerts/selectors.js @@ -383,10 +383,10 @@ export const parseConfig = createSelector( return { ...getChartConfig(colors, moment(latest), {}, ''), xAxis: { + dataKey: 'month', tickCount: 12, - interval: 4, - scale: 'point', - tickFormatter: (t) => moment(t).format('MMM'), + interval: 0, + tickFormatter: (t) => t.charAt(0).toUpperCase() + t.slice(1), ...(typeof endIndex === 'number' && typeof startIndex === 'number' && endIndex - startIndex < 10 && { diff --git a/components/widgets/utils/data.js b/components/widgets/utils/data.js index cd4f535694..3678347e17 100644 --- a/components/widgets/utils/data.js +++ b/components/widgets/utils/data.js @@ -5,7 +5,6 @@ import concat from 'lodash/concat'; import maxBy from 'lodash/maxBy'; import minBy from 'lodash/minBy'; import sumBy from 'lodash/sumBy'; -import upperCase from 'lodash/upperCase'; import moment from 'moment'; import range from 'lodash/range'; @@ -309,17 +308,26 @@ export const getCumulativeStatsData = (data) => { }; export const getDatesData = (data) => - data.map((d) => ({ - ...d, - date: d.date - ? moment(d.date).format('YYYY-MM-DD') - : moment() - .year(d.year) - .isoWeek(d.week) - .startOf('isoWeek') - .format('YYYY-MM-DD'), - month: upperCase(moment().year(d.year).isoWeek(d.week).format('MMM')), - })); + data.map((d, index) => { + const firstDayOfWeek = moment() + .year(d.year) + .isoWeek(d.week) + .startOf('isoWeek'); + + return { + ...d, + ...((firstDayOfWeek.date() <= 7 || index === 0) && { + month: moment().year(d.year).isoWeek(d.week).format('MMM'), + }), + date: d.date + ? moment(d.date).format('YYYY-MM-DD') + : moment() + .year(d.year) + .isoWeek(d.week) + .startOf('isoWeek') + .format('YYYY-MM-DD'), + }; + }); export const getChartConfig = ( colors, From 0a890d9031801cc1f77700cd4a94c8690adb1afb Mon Sep 17 00:00:00 2001 From: Willian Viana Date: Thu, 11 Jul 2024 15:01:44 -0300 Subject: [PATCH 5/7] chore(alerts): bring month back to all items and set a special property monthLabel to XAxis dataKey --- components/widgets/fires/fires-alerts/selectors.js | 2 +- components/widgets/utils/data.js | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/components/widgets/fires/fires-alerts/selectors.js b/components/widgets/fires/fires-alerts/selectors.js index 4f5f9ac710..94633b2521 100644 --- a/components/widgets/fires/fires-alerts/selectors.js +++ b/components/widgets/fires/fires-alerts/selectors.js @@ -383,7 +383,7 @@ export const parseConfig = createSelector( return { ...getChartConfig(colors, moment(latest), {}, ''), xAxis: { - dataKey: 'month', + dataKey: 'monthLabel', tickCount: 12, interval: 0, tickFormatter: (t) => t.charAt(0).toUpperCase() + t.slice(1), diff --git a/components/widgets/utils/data.js b/components/widgets/utils/data.js index 3678347e17..566b604d71 100644 --- a/components/widgets/utils/data.js +++ b/components/widgets/utils/data.js @@ -7,6 +7,7 @@ import minBy from 'lodash/minBy'; import sumBy from 'lodash/sumBy'; import moment from 'moment'; import range from 'lodash/range'; +import upperCase from 'lodash'; const translateMeans = (means, latest) => { if (!means || !means.length) return null; @@ -317,7 +318,7 @@ export const getDatesData = (data) => return { ...d, ...((firstDayOfWeek.date() <= 7 || index === 0) && { - month: moment().year(d.year).isoWeek(d.week).format('MMM'), + monthLabel: moment().year(d.year).isoWeek(d.week).format('MMM'), }), date: d.date ? moment(d.date).format('YYYY-MM-DD') @@ -326,6 +327,7 @@ export const getDatesData = (data) => .isoWeek(d.week) .startOf('isoWeek') .format('YYYY-MM-DD'), + month: upperCase(moment().year(d.year).isoWeek(d.week).format('MMM')), }; }); From 4cf606b09e0b1fec4f78630c489a53a6bebd31ed Mon Sep 17 00:00:00 2001 From: Willian Viana Date: Fri, 12 Jul 2024 15:29:07 -0300 Subject: [PATCH 6/7] fix(alerts): remove wrong reduce Now when the user selects confidence 'all' we count the alerts for the three different confidence levels --- components/widgets/fires/fires-alerts/selectors.js | 11 ++--------- components/widgets/utils/data.js | 7 ++++--- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/components/widgets/fires/fires-alerts/selectors.js b/components/widgets/fires/fires-alerts/selectors.js index 94633b2521..b10b038640 100644 --- a/components/widgets/fires/fires-alerts/selectors.js +++ b/components/widgets/fires/fires-alerts/selectors.js @@ -59,7 +59,7 @@ const generateYearWeekArray = (startDate, endDate) => { const getAllAlerts = createSelector([getAlerts], (alerts) => { if (!alerts) return null; - const filler = { iso: alerts[0]?.iso, alert__count: 0 }; + const filler = { iso: alerts[0]?.iso, alert__count: 0, confidence__cat: 'h' }; const minYear = '2012'; const maxYear = new Date(); @@ -180,18 +180,11 @@ export const getData = createSelector( ? yearDataByWeek[i].length - 1 : 0; - let objectsReduced = {}; - for (let index = 0; index <= yearDataLength; index += 1) { if (yearDataByWeek[i]) { - objectsReduced = Object.assign(objectsReduced, { - ...yearDataByWeek[i][index], - count: yearDataByWeek[i][index].count, - }); + formattedData.push(yearDataByWeek[i][index]); } } - - formattedData.push(objectsReduced); } }); diff --git a/components/widgets/utils/data.js b/components/widgets/utils/data.js index 566b604d71..bb01389880 100644 --- a/components/widgets/utils/data.js +++ b/components/widgets/utils/data.js @@ -317,9 +317,10 @@ export const getDatesData = (data) => return { ...d, - ...((firstDayOfWeek.date() <= 7 || index === 0) && { - monthLabel: moment().year(d.year).isoWeek(d.week).format('MMM'), - }), + ...((firstDayOfWeek.date() <= 7 || index === 0) && + d?.confidence__cat === 'h' && { + monthLabel: moment().year(d.year).isoWeek(d.week).format('MMM'), + }), date: d.date ? moment(d.date).format('YYYY-MM-DD') : moment() From 8a4ec2081612886f31465d78a9e01338d15cd85b Mon Sep 17 00:00:00 2001 From: Willian Viana Date: Mon, 15 Jul 2024 19:02:39 -0300 Subject: [PATCH 7/7] fix(alerts): aggregated different confidence levels --- .../widgets/fires/fires-alerts/selectors.js | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/components/widgets/fires/fires-alerts/selectors.js b/components/widgets/fires/fires-alerts/selectors.js index b10b038640..1a6df53fd4 100644 --- a/components/widgets/fires/fires-alerts/selectors.js +++ b/components/widgets/fires/fires-alerts/selectors.js @@ -119,10 +119,10 @@ const getAllAlerts = createSelector([getAlerts], (alerts) => { }); export const getData = createSelector( - [getAllAlerts, getLatest], - (data, latest) => { + [getAllAlerts, getLatest, getOptionsSelected], + (data, latest, options) => { if (!data || isEmpty(data)) return null; - + const { confidence } = options; const parsedData = data.map((d) => ({ ...d, count: d.alert__count || d.area_ha || 0, @@ -176,15 +176,34 @@ export const getData = createSelector( return; } + const alerts = []; const yearDataLength = yearDataByWeek[i] ? yearDataByWeek[i].length - 1 : 0; for (let index = 0; index <= yearDataLength; index += 1) { if (yearDataByWeek[i]) { - formattedData.push(yearDataByWeek[i][index]); + alerts.push(yearDataByWeek[i][index]); } } + + if (confidence.value === 'h') { + formattedData.push(...alerts); + } else { + const allConfidencesAggregated = alerts.reduce( + (acc, curr) => { + return { + ...curr, + confidence__cat: '', // high, low and normal + alert__count: acc?.alert__count + curr?.alert__count, + count: acc?.alert__count + curr?.alert__count, + }; + }, + { alert__count: 0 } + ); + + formattedData.push(allConfidencesAggregated); + } } });