Skip to content

Commit e55bf09

Browse files
feat: add new system settings and improve period type handling (#1456)
This PR does a few things related to data output period types and related system settings: Moves the "Default relative period for analysis" setting further down, next to other related settings, as it now depends on the relevant period type to be enabled. There is a system for defining the minimum api version per setting, but this PR adds version toggling for dropdown options as well. In 43 we're adding some more options to an existing setting that depend on the backend. Do you approve this solution? Adds a new system setting for relative weekly (works the same way as the one for financial years). Sorting period types per section chronologically instead of alphabetically. Warning message for system settings that rely on the relevant period type to be enabled.
1 parent e3d7453 commit e55bf09

File tree

7 files changed

+267
-63
lines changed

7 files changed

+267
-63
lines changed

i18n/en.pot

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ msgstr ""
55
"Content-Type: text/plain; charset=utf-8\n"
66
"Content-Transfer-Encoding: 8bit\n"
77
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
8-
"POT-Creation-Date: 2026-01-13T22:53:28.645Z\n"
9-
"PO-Revision-Date: 2026-01-13T22:53:28.645Z\n"
8+
"POT-Creation-Date: 2026-02-20T19:17:48.484Z\n"
9+
"PO-Revision-Date: 2026-02-20T19:17:48.484Z\n"
1010

1111
msgid "Failed to load: {{error}}"
1212
msgstr "Failed to load: {{error}}"
@@ -393,6 +393,9 @@ msgstr "No settings matched the search term"
393393
msgid "Search results"
394394
msgstr "Search results"
395395

396+
msgid "This period type is currently disabled in the period type settings above"
397+
msgstr "This period type is currently disabled in the period type settings above"
398+
396399
msgid "Maximum number of analytics records"
397400
msgstr "Maximum number of analytics records"
398401

@@ -463,6 +466,30 @@ msgstr "Gather analytical object statistics in dashboard views"
463466
msgid "Include passive dashboard views in usage analytics statistics"
464467
msgstr "Include passive dashboard views in usage analytics statistics"
465468

469+
msgid "Hide daily periods"
470+
msgstr "Hide daily periods"
471+
472+
msgid "Hidden period types in analytics apps"
473+
msgstr "Hidden period types in analytics apps"
474+
475+
msgid "Hide weekly periods"
476+
msgstr "Hide weekly periods"
477+
478+
msgid "Hide biweekly periods"
479+
msgstr "Hide biweekly periods"
480+
481+
msgid "Hide monthly periods"
482+
msgstr "Hide monthly periods"
483+
484+
msgid "Hide bimonthly periods"
485+
msgstr "Hide bimonthly periods"
486+
487+
msgid "Period types"
488+
msgstr "Period types"
489+
490+
msgid "Allowed period types"
491+
msgstr "Allowed period types"
492+
466493
msgid "Default relative period for analysis"
467494
msgstr "Default relative period for analysis"
468495

@@ -556,33 +583,12 @@ msgstr "Last financial year"
556583
msgid "Last 5 financial years"
557584
msgstr "Last 5 financial years"
558585

559-
msgid "Hide daily periods"
560-
msgstr "Hide daily periods"
561-
562-
msgid "Hidden period types in analytics apps"
563-
msgstr "Hidden period types in analytics apps"
564-
565-
msgid "Hide weekly periods"
566-
msgstr "Hide weekly periods"
567-
568-
msgid "Hide biweekly periods"
569-
msgstr "Hide biweekly periods"
570-
571-
msgid "Hide monthly periods"
572-
msgstr "Hide monthly periods"
573-
574-
msgid "Hide bimonthly periods"
575-
msgstr "Hide bimonthly periods"
576-
577-
msgid "Period types"
578-
msgstr "Period types"
579-
580-
msgid "Allowed period types"
581-
msgstr "Allowed period types"
582-
583586
msgid "Financial year relative period start month"
584587
msgstr "Financial year relative period start month"
585588

589+
msgid "Weekly relative period start day"
590+
msgstr "Weekly relative period start day"
591+
586592
msgid "Cache strategy"
587593
msgstr "Cache strategy"
588594

src/app.component.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ class AppComponent extends React.Component {
211211
<SettingsFields
212212
category={this.state.category}
213213
currentSettings={this.state.currentSettings}
214+
apiVersion={this.props.apiVersion}
214215
/>
215216
</form>
216217
</div>

src/form-fields/drop-down.jsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ import SelectField from 'material-ui/SelectField'
1111
import PropTypes from 'prop-types'
1212
import React from 'react'
1313

14+
const helpTextStyle = {
15+
fontSize: '12px',
16+
color: '#d32f2f',
17+
margin: '-4px 0 0',
18+
}
19+
1420
class DropDown extends React.Component {
1521
static propTypes = {
1622
value: PropTypes.string.isRequired,
@@ -20,6 +26,7 @@ class DropDown extends React.Component {
2026
PropTypes.bool,
2127
]),
2228
emptyLabel: PropTypes.string,
29+
helpText: PropTypes.node,
2330
includeEmpty: PropTypes.bool,
2431
menuItems: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
2532
noOptionsLabel: PropTypes.string,
@@ -103,6 +110,7 @@ class DropDown extends React.Component {
103110
noOptionsLabel,
104111
isRequired,
105112
warning,
113+
helpText,
106114
/* eslint-enable no-unused-vars, react/prop-types */
107115
...other
108116
} = this.props
@@ -145,6 +153,7 @@ class DropDown extends React.Component {
145153
/>
146154
)}
147155
</SelectField>
156+
{helpText && <p style={helpTextStyle}>{helpText}</p>}
148157
</>
149158
)
150159
}

src/period-types/PeriodTypes.component.jsx

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import i18n from '@dhis2/d2-i18n'
33
import { CenteredContent, CircularLoader } from '@dhis2/ui'
44
import CheckboxMaterial from 'material-ui/Checkbox'
55
import React, { useState, useEffect } from 'react'
6+
import configOptionStore from '../configOptionStore.js'
67
import settingsActions from '../settingsActions.js'
78
import styles from './PeriodTypes.module.css'
89

@@ -169,6 +170,32 @@ const getGroupLabel = (frequencyOrder) => {
169170
return labels[frequencyOrder] || i18n.t('Other')
170171
}
171172

173+
const periodTypeOrder = {
174+
Daily: 1,
175+
Weekly: 1,
176+
WeeklyWednesday: 2,
177+
WeeklyThursday: 3,
178+
WeeklyFriday: 4,
179+
WeeklySaturday: 5,
180+
WeeklySunday: 6,
181+
BiWeekly: 1,
182+
Monthly: 1,
183+
BiMonthly: 1,
184+
Quarterly: 1,
185+
QuarterlyNov: 2,
186+
SixMonthly: 1,
187+
SixMonthlyApril: 2,
188+
SixMonthlyNov: 3,
189+
Yearly: 1,
190+
FinancialFeb: 2,
191+
FinancialApril: 3,
192+
FinancialJuly: 4,
193+
FinancialAug: 5,
194+
FinancialSep: 6,
195+
FinancialOct: 7,
196+
FinancialNov: 8,
197+
}
198+
172199
const groupByFrequency = (periodTypes) => {
173200
const groups = {}
174201
periodTypes.forEach((pt) => {
@@ -182,9 +209,16 @@ const groupByFrequency = (periodTypes) => {
182209
}
183210
groups[freq].periodTypes.push(pt)
184211
})
185-
return Object.values(groups).sort(
212+
const sorted = Object.values(groups).sort(
186213
(a, b) => a.frequencyOrder - b.frequencyOrder
187214
)
215+
sorted.forEach((group) => {
216+
group.periodTypes.sort(
217+
(a, b) =>
218+
(periodTypeOrder[a.name] || 0) - (periodTypeOrder[b.name] || 0)
219+
)
220+
})
221+
return sorted
188222
}
189223

190224
const PeriodTypes = () => {
@@ -196,6 +230,10 @@ const PeriodTypes = () => {
196230
useEffect(() => {
197231
if (data?.dataOutputPeriodTypes) {
198232
setAllowedPeriodTypes(data.dataOutputPeriodTypes)
233+
configOptionStore.setState({
234+
...configOptionStore.state,
235+
dataOutputPeriodTypes: data.dataOutputPeriodTypes,
236+
})
199237
}
200238
}, [data?.dataOutputPeriodTypes])
201239

@@ -240,6 +278,10 @@ const PeriodTypes = () => {
240278
}
241279

242280
setAllowedPeriodTypes(updatedPeriodTypes)
281+
configOptionStore.setState({
282+
...configOptionStore.state,
283+
dataOutputPeriodTypes: updatedPeriodTypes,
284+
})
243285
settingsActions.showSnackbarMessage(i18n.t('Settings updated'))
244286
} catch (error) {
245287
console.error('Failed to update period types:', error)

src/settingsCategories.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,6 @@ export const categories = {
7575
icon: 'equalizer',
7676
pageLabel: i18n.t('Analytics settings'),
7777
settings: [
78-
{
79-
setting: 'keyAnalysisRelativePeriod',
80-
},
8178
{
8279
setting: 'keyAnalysisDisplayProperty',
8380
},
@@ -108,6 +105,13 @@ export const categories = {
108105
setting: 'dataOutputPeriodTypes',
109106
minimumApiVersion: 43,
110107
},
108+
{
109+
setting: 'keyAnalysisRelativePeriod',
110+
},
111+
{
112+
setting: 'analyticsWeeklyStart',
113+
minimumApiVersion: 43,
114+
},
111115
{
112116
setting: 'analyticsFinancialYearStart',
113117
},

src/settingsFields.component.jsx

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,17 @@ function addConditionallyHiddenStyles(mapping) {
117117
return settingsValue === currentValue ? { display: 'none' } : {}
118118
}
119119

120-
function getMenuItems(mapping) {
120+
function getMenuItems(mapping, apiVersion) {
121121
const sourceMenuItems =
122122
(configOptionStore.state && configOptionStore.state[mapping.source]) ||
123123
[]
124124

125-
const optionsMenuItems = Object.entries(mapping.options || []).map(
125+
const options =
126+
typeof mapping.options === 'function'
127+
? mapping.options(apiVersion)
128+
: mapping.options
129+
130+
const optionsMenuItems = Object.entries(options || []).map(
126131
([id, displayName]) => ({
127132
id,
128133
displayName,
@@ -141,7 +146,8 @@ class SettingsFields extends React.Component {
141146
componentDidMount() {
142147
this.subscriptions = []
143148
this.subscriptions.push(
144-
settingsStore.subscribe(() => this.forceUpdate())
149+
settingsStore.subscribe(() => this.forceUpdate()),
150+
configOptionStore.subscribe(() => this.forceUpdate())
145151
)
146152
}
147153

@@ -158,7 +164,7 @@ class SettingsFields extends React.Component {
158164
}
159165
}
160166

161-
fieldForMapping({ mapping, fieldBase, key, d2 }) {
167+
fieldForMapping({ mapping, fieldBase, key, d2, apiVersion }) {
162168
switch (mapping.type) {
163169
case 'textfield':
164170
case undefined:
@@ -178,15 +184,23 @@ class SettingsFields extends React.Component {
178184
}),
179185
})
180186

181-
case 'dropdown':
187+
case 'dropdown': {
182188
if (mapping.includeEmpty && fieldBase.value === '') {
183189
fieldBase.value = 'null'
184190
}
185191

192+
const helpText =
193+
typeof mapping.helpText === 'function'
194+
? mapping.helpText(fieldBase.value, {
195+
configOptions: configOptionStore.getState(),
196+
settings: settingsStore.state,
197+
})
198+
: mapping.helpText
199+
186200
return Object.assign({}, fieldBase, {
187201
component: SelectField,
188202
props: Object.assign({}, fieldBase.props, {
189-
menuItems: getMenuItems(mapping),
203+
menuItems: getMenuItems(mapping, apiVersion),
190204
includeEmpty: !!mapping.includeEmpty,
191205
emptyLabel:
192206
(mapping.includeEmpty && mapping.emptyLabel) ||
@@ -195,8 +209,11 @@ class SettingsFields extends React.Component {
195209
warning:
196210
(mapping.showWarning && mapping.warning) ||
197211
undefined,
212+
helpText,
198213
}),
199214
})
215+
}
216+
200217
case 'emailCheckbox': {
201218
const emailConfigured = isEmailConfigured(d2)
202219
const explanatoryText =
@@ -382,7 +399,13 @@ class SettingsFields extends React.Component {
382399
validators,
383400
}
384401

385-
return this.fieldForMapping({ mapping, fieldBase, key, d2 })
402+
return this.fieldForMapping({
403+
mapping,
404+
fieldBase,
405+
key,
406+
d2,
407+
apiVersion: this.props.apiVersion,
408+
})
386409
})
387410
.filter((f) => f && !!f.name)
388411
.map((field) => {
@@ -438,6 +461,7 @@ class SettingsFields extends React.Component {
438461
}
439462

440463
SettingsFields.propTypes = {
464+
apiVersion: PropTypes.number.isRequired,
441465
category: PropTypes.string.isRequired,
442466
currentSettings: PropTypes.arrayOf(PropTypes.string).isRequired,
443467
}

0 commit comments

Comments
 (0)