From b4a5d537d73ff10325f568dadf38794e7c7ef9d0 Mon Sep 17 00:00:00 2001 From: Edoardo Sabadelli Date: Tue, 5 Mar 2024 16:44:10 +0100 Subject: [PATCH] feat: support Outlier table visualization type (DHIS2-13858) (#2942) --- i18n/en.pot | 142 ++- package.json | 7 +- src/PluginWrapper.js | 7 +- src/actions/ui.js | 11 + src/api/analytics.js | 91 +- src/assets/OutlierTableIcon.js | 37 + .../DimensionsPanel/Dialogs/DialogManager.js | 69 +- .../Dialogs/__tests__/DialogManager.spec.js | 2 + .../__snapshots__/DialogManager.spec.js.snap | 2 + .../DimensionsPanel/DndDimensionList.js | 39 +- .../DownloadMenu/AdvancedSubMenu.js | 1 + src/components/DownloadMenu/DownloadMenu.js | 37 +- src/components/DownloadMenu/useDownload.js | 151 +-- .../InterpretationModal.js | 4 +- src/components/Layout/Chip.js | 36 +- src/components/Layout/Layout.js | 5 +- .../Layout/OutlierTable/OutlierTableLayout.js | 30 + .../styles/OutlierTableLayout.style.js | 5 + src/components/Layout/TooltipContent.js | 7 +- src/components/MenuBar/MenuBar.js | 97 +- src/components/Visualization/Visualization.js | 50 +- .../Options/OutlierDetectionMethod.js | 2 +- .../VisualizationOptions/Options/Outliers.js | 3 + .../Options/OutliersForOutlierTable.js | 123 +++ .../Options/OutliersMaxResults.js | 72 ++ .../VisualizationPlugin/OutlierTablePlugin.js | 299 ++++++ .../VisualizationPlugin.js | 103 ++- .../VisualizationPluginWrapper.js | 52 ++ .../styles/OutlierTablePlugin.module.css | 64 ++ .../styles/VisualizationPlugin.module.css | 1 + .../VisualizationTypeSelector/ListItemIcon.js | 4 + .../VisualizationTypeSelector.js | 4 +- src/modules/analytics.js | 96 +- src/modules/current.js | 51 ++ src/modules/error.js | 12 + src/modules/fetchData.js | 13 + src/modules/fields/baseFields.js | 1 + src/modules/layoutValidation.js | 15 + src/modules/options.js | 17 +- src/modules/options/config.js | 4 + src/modules/options/outlierTableConfig.js | 40 + src/modules/ui.js | 47 + src/modules/visualization.js | 19 +- src/reducers/current.js | 8 + src/reducers/ui.js | 50 +- yarn.lock | 858 +++++++++--------- 46 files changed, 2135 insertions(+), 653 deletions(-) create mode 100644 src/assets/OutlierTableIcon.js create mode 100644 src/components/Layout/OutlierTable/OutlierTableLayout.js create mode 100644 src/components/Layout/OutlierTable/styles/OutlierTableLayout.style.js create mode 100644 src/components/VisualizationOptions/Options/OutliersForOutlierTable.js create mode 100644 src/components/VisualizationOptions/Options/OutliersMaxResults.js create mode 100644 src/components/VisualizationPlugin/OutlierTablePlugin.js create mode 100644 src/components/VisualizationPlugin/VisualizationPluginWrapper.js create mode 100644 src/components/VisualizationPlugin/styles/OutlierTablePlugin.module.css create mode 100644 src/modules/options/outlierTableConfig.js diff --git a/i18n/en.pot b/i18n/en.pot index 10985d66d0..437ec605a4 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2023-11-13T12:11:28.959Z\n" -"PO-Revision-Date: 2023-11-13T12:11:28.959Z\n" +"POT-Creation-Date: 2024-03-01T08:28:43.727Z\n" +"PO-Revision-Date: 2024-03-01T08:28:43.727Z\n" msgid "All items" msgstr "All items" @@ -43,11 +43,11 @@ msgid "Add to {{axisName}}" msgstr "Add to {{axisName}}" msgid "" -"'{{visualizationType}}' is intended to show a single data item. Only the " -"first item will be used and saved." +"'{{visualizationType}}' is intended to show a single item for this type of " +"dimension. Only the first item will be used and saved." msgstr "" -"'{{visualizationType}}' is intended to show a single data item. Only the " -"first item will be used and saved." +"'{{visualizationType}}' is intended to show a single item for this type of " +"dimension. Only the first item will be used and saved." msgid "" "'{{visualiationType}}' is intended to show maximum {{maxNumber}} number of " @@ -63,6 +63,13 @@ msgstr "" "'Scatter' is intended to show a single data item per axis. Only the first " "item will be used and saved." +msgid "" +"'Outlier table' shows values from data elements only. Only data elements " +"will be used and saved." +msgstr "" +"'Outlier table' shows values from data elements only. Only data elements " +"will be used and saved." + msgid "Vertical" msgstr "Vertical" @@ -183,11 +190,14 @@ msgstr "horizontal" msgid "None selected" msgstr "None selected" +msgid "None in use" +msgstr "None in use" + msgid "Only '{{- name}}' in use" msgstr "Only '{{- name}}' in use" -msgid "Only '{{number}}' in use" -msgstr "Only '{{number}}' in use" +msgid "Only {{number}} in use" +msgstr "Only {{number}} in use" msgid "All items are selected" msgstr "All items are selected" @@ -581,6 +591,16 @@ msgstr "" msgid "Outlier detection method" msgstr "Outlier detection method" +msgid "Max results" +msgstr "Max results" + +msgid "" +"The maximum number of outlier values to show in the table. Must be between " +"1-500." +msgstr "" +"The maximum number of outlier values to show in the table. Must be between " +"1-500." + msgid "Organisation unit" msgstr "Organisation unit" @@ -706,12 +726,101 @@ msgstr "Change org unit" msgid "{{level}} level in {{orgunit}}" msgstr "{{level}} level in {{orgunit}}" +msgid "Sort ascending by {{columnName}} and update" +msgstr "Sort ascending by {{columnName}} and update" + +msgid "Sort descending by {{columnName}} and update" +msgstr "Sort descending by {{columnName}} and update" + msgid "Open as Map" msgstr "Open as Map" msgid "Visually plot data on a world map. Data elements use separate map layers." msgstr "Visually plot data on a world map. Data elements use separate map layers." +msgid "Category option combination" +msgstr "Category option combination" + +msgid "Absolute deviation" +msgstr "Absolute deviation" + +msgid "" +"A measure of the absolute difference between each data point and a central " +"value, usually the mean or median, providing a straightforward " +"understanding of dispersion in the dataset." +msgstr "" +"A measure of the absolute difference between each data point and a central " +"value, usually the mean or median, providing a straightforward " +"understanding of dispersion in the dataset." + +msgid "" +"A measure of how far a data point deviates from the median, using the " +"median absolute deviation instead of the standard deviation, making it " +"robust against outliers." +msgstr "" +"A measure of how far a data point deviates from the median, using the " +"median absolute deviation instead of the standard deviation, making it " +"robust against outliers." + +msgid "Median" +msgstr "Median" + +msgid "" +"The middle value in a dataset when the values are arranged in ascending or " +"descending order. It's a robust measure of central tendency that is less " +"affected by outliers compared to the mean." +msgstr "" +"The middle value in a dataset when the values are arranged in ascending or " +"descending order. It's a robust measure of central tendency that is less " +"affected by outliers compared to the mean." + +msgid "Median absolute deviation" +msgstr "Median absolute deviation" + +msgid "" +"A robust measure of variability, found by calculating the median of the " +"absolute differences between each data point and the overall median. It's " +"less influenced by outliers compared to other measures like the standard " +"deviation." +msgstr "" +"A robust measure of variability, found by calculating the median of the " +"absolute differences between each data point and the overall median. It's " +"less influenced by outliers compared to other measures like the standard " +"deviation." + +msgid "Z-score" +msgstr "Z-score" + +msgid "" +"A measure of how many standard deviations a data point is from the mean of " +"a dataset, providing insight into how unusual or typical that data point is " +"relative to the rest of the distribution." +msgstr "" +"A measure of how many standard deviations a data point is from the mean of " +"a dataset, providing insight into how unusual or typical that data point is " +"relative to the rest of the distribution." + +msgid "Mean" +msgstr "Mean" + +msgid "Average of the value over time." +msgstr "Average of the value over time." + +msgid "" +"A measure of how dispersed the data is in relation to the mean. Low " +"standard deviation indicates data are clustered tightly around the mean, " +"and high standard deviation indicates data are more spread out." +msgstr "" +"A measure of how dispersed the data is in relation to the mean. Low " +"standard deviation indicates data are clustered tightly around the mean, " +"and high standard deviation indicates data are more spread out." + +msgid "Minimum score threshold" +msgstr "Minimum score threshold" + +msgid "Maximum score threshold" +msgstr "Maximum score threshold" + msgid "Not supported when using cumulative values" msgstr "Not supported when using cumulative values" @@ -855,6 +964,12 @@ msgstr "" msgid "There's a syntax problem with the analytics request." msgstr "There's a syntax problem with the analytics request." +msgid "No outliers found" +msgstr "No outliers found" + +msgid "There were no outliers found for the selected data items and options." +msgstr "There were no outliers found for the selected data items and options." + msgid "or" msgstr "or" @@ -1111,11 +1226,14 @@ msgid "Display a single value. Recommend relative period to show latest data." msgstr "Display a single value. Recommend relative period to show latest data." msgid "" -"View the relationship between two data items at a place or time. " -"Recommended for finding outliers." +"Compare the relationship between two data items across multiple places. " +"Recommended for visualizing outliers." msgstr "" -"View the relationship between two data items at a place or time. " -"Recommended for finding outliers." +"Compare the relationship between two data items across multiple places. " +"Recommended for visualizing outliers." + +msgid "Automatically identify extreme outliers based on historical data." +msgstr "Automatically identify extreme outliers based on historical data." msgid "Weeks per year" msgstr "Weeks per year" diff --git a/package.json b/package.json index 2151d1306e..005cecc5dc 100644 --- a/package.json +++ b/package.json @@ -41,12 +41,12 @@ "start-server-and-test": "^2.0.0" }, "dependencies": { - "@dhis2/analytics": "^26.3.0", + "@dhis2/analytics": "999.9.9-outlier-table.alpha.4", "@dhis2/app-runtime": "^3.7.0", "@dhis2/app-runtime-adapter-d2": "^1.1.0", "@dhis2/app-service-datastore": "^1.0.0-beta.3", "@dhis2/d2-i18n": "^1.1.0", - "@dhis2/ui": "^8.7.6", + "@dhis2/ui": "^9.2.0", "@krakenjs/post-robot": "^11.0.0", "d2": "^31.9.1", "decode-uri-component": "^0.2.2", @@ -66,5 +66,8 @@ "reselect": "^4.1.7", "styled-jsx": "^4", "whatwg-fetch": "^3.6.2" + }, + "resolutions": { + "@dhis2/ui": "^9.2.0" } } diff --git a/src/PluginWrapper.js b/src/PluginWrapper.js index 52ee038764..0365f0af28 100644 --- a/src/PluginWrapper.js +++ b/src/PluginWrapper.js @@ -4,7 +4,7 @@ import postRobot from '@krakenjs/post-robot' import debounce from 'lodash-es/debounce' import PropTypes from 'prop-types' import React, { useEffect, useLayoutEffect, useState } from 'react' -import { VisualizationPlugin } from './components/VisualizationPlugin/VisualizationPlugin.js' +import { VisualizationPluginWrapper } from './components/VisualizationPlugin/VisualizationPluginWrapper.js' import { getPWAInstallationStatus } from './modules/getPWAInstallationStatus.js' const LoadingMask = () => { @@ -128,7 +128,10 @@ const PluginWrapper = () => { cacheNow={propsFromParent.recordOnNextLoad} isParentCached={propsFromParent.isParentCached} > - + diff --git a/src/actions/ui.js b/src/actions/ui.js index df704580f7..5d1f04e8c4 100644 --- a/src/actions/ui.js +++ b/src/actions/ui.js @@ -2,6 +2,7 @@ import { getDisabledOptions } from '../modules/disabledOptions.js' import { SET_UI, CLEAR_UI, + CLEAR_UI_DATA_SORTING, SET_UI_FROM_VISUALIZATION, SET_UI_TYPE, SET_UI_DISABLED_OPTIONS, @@ -23,6 +24,7 @@ import { UPDATE_UI_SERIES_ITEM, SET_UI_OPTION, SET_UI_OPTION_FONT_STYLE, + SET_UI_DATA_SORTING, sGetUiType, sGetUiOptions, } from '../reducers/ui.js' @@ -67,6 +69,15 @@ export const acSetUiOptionFontStyle = (value) => ({ value, }) +export const acClearUiDataSorting = () => ({ + type: CLEAR_UI_DATA_SORTING, +}) + +export const acSetUiDataSorting = (value) => ({ + type: SET_UI_DATA_SORTING, + value, +}) + export const acUpdateUiSeriesItem = (value) => ({ type: UPDATE_UI_SERIES_ITEM, value, diff --git a/src/api/analytics.js b/src/api/analytics.js index f6d3b338de..60504ee269 100644 --- a/src/api/analytics.js +++ b/src/api/analytics.js @@ -1,13 +1,25 @@ import { Analytics, - VIS_TYPE_PIVOT_TABLE, layoutGetDimensionItems, + VIS_TYPE_PIVOT_TABLE, + DIMENSION_ID_DATA, DIMENSION_ID_PERIOD, DAILY, WEEKLY, WEEKS_THIS_YEAR, } from '@dhis2/analytics' -import { getRelativePeriodTypeUsed } from '../modules/analytics.js' +import { + METHOD_MODIFIED_Z_SCORE, + METHOD_STANDARD_Z_SCORE, + OUTLIER_METHOD_PROP, + OUTLIER_THRESHOLD_PROP, +} from '../components/VisualizationOptions/Options/OutliersForOutlierTable.js' +import { OUTLIER_MAX_RESULTS_PROP } from '../components/VisualizationOptions/Options/OutliersMaxResults.js' +import { + getRelativePeriodTypeUsed, + getOutlierTableDimensionIdHeaderMap, +} from '../modules/analytics.js' +import { getSortingFromVisualization } from '../modules/ui.js' const periodId = DIMENSION_ID_PERIOD @@ -24,6 +36,81 @@ export const apiFetchAnalytics = async (dataEngine, visualization, options) => { return [new analyticsEngine.response(rawResponse)] } +export const getAnalyticsRequestForOutlierTable = ({ + analyticsEngine, + visualization, + options, + forDownload = false, +}) => { + const dimensionIdHeaderMap = getOutlierTableDimensionIdHeaderMap(options) + + const parameters = { + ...options, + maxResults: visualization.outlierAnalysis[OUTLIER_MAX_RESULTS_PROP], + algorithm: + visualization.outlierAnalysis[OUTLIER_METHOD_PROP] === + METHOD_STANDARD_Z_SCORE + ? 'Z_SCORE' + : visualization.outlierAnalysis[OUTLIER_METHOD_PROP], + threshold: visualization.outlierAnalysis[OUTLIER_THRESHOLD_PROP], + } + + const columns = visualization.columns || [] + const headers = [] + + columns.forEach(({ dimension, items }) => { + parameters[dimension] = items.map(({ id }) => id).join(',') + + headers.push(forDownload ? dimension : dimensionIdHeaderMap[dimension]) + + if (dimension === DIMENSION_ID_DATA) { + headers.push('cocname') + } + }) + + headers.push('value') + + switch (visualization.outlierAnalysis.outlierMethod) { + case METHOD_MODIFIED_Z_SCORE: + headers.push('median', 'modifiedzscore', 'medianabsdeviation') + break + case METHOD_STANDARD_Z_SCORE: + headers.push('mean', 'zscore', 'stddev') + } + + headers.push('lowerbound', 'upperbound') + + parameters.headers = headers.join(',') + + // sorting + const sorting = getSortingFromVisualization(visualization) + + if (sorting) { + parameters.orderBy = sorting.dimension + parameters.sortOrder = sorting.direction + } + + return new analyticsEngine.request().withParameters(parameters) +} + +export const apiFetchAnalyticsForOutlierTable = async ( + dataEngine, + visualization, + options +) => { + const analyticsEngine = Analytics.getAnalytics(dataEngine) + + const req = getAnalyticsRequestForOutlierTable({ + analyticsEngine, + visualization, + options, + }) + + const rawResponse = await analyticsEngine.aggregate.getOutliersData(req) + + return [new analyticsEngine.response(rawResponse)] +} + export const apiFetchAnalyticsForYearOverYear = async ( dataEngine, visualization, diff --git a/src/assets/OutlierTableIcon.js b/src/assets/OutlierTableIcon.js new file mode 100644 index 0000000000..1e0bca1e60 --- /dev/null +++ b/src/assets/OutlierTableIcon.js @@ -0,0 +1,37 @@ +import PropTypes from 'prop-types' +import React from 'react' + +const OutlierTableIcon = ({ + style = { paddingRight: '8px', width: 24, height: 24 }, +}) => ( + + + + + + + + + +) + +OutlierTableIcon.propTypes = { + style: PropTypes.object, +} + +export default OutlierTableIcon diff --git a/src/components/DimensionsPanel/Dialogs/DialogManager.js b/src/components/DimensionsPanel/Dialogs/DialogManager.js index 1cbf1e885f..e3844578d0 100644 --- a/src/components/DimensionsPanel/Dialogs/DialogManager.js +++ b/src/components/DimensionsPanel/Dialogs/DialogManager.js @@ -4,9 +4,13 @@ import { PeriodDimension, OrgUnitDimension, ouIdHelper, + dataTypeMap, DIMENSION_ID_DATA, DIMENSION_ID_PERIOD, DIMENSION_ID_ORGUNIT, + DIMENSION_TYPE_DATA_ELEMENT, + DIMENSION_TYPE_DATA_ELEMENT_OPERAND, + getDimensionMaxNumberOfItems, getAxisMaxNumberOfItems, getDisplayNameByVisType, filterOutPredefinedDimensions, @@ -21,6 +25,7 @@ import { MONTHLY, BIMONTHLY, ALL_DYNAMIC_DIMENSION_ITEMS, + VIS_TYPE_OUTLIER_TABLE, } from '@dhis2/analytics' import i18n from '@dhis2/d2-i18n' import { @@ -258,7 +263,7 @@ export class DialogManager extends Component { } renderDialogContent = () => { - const { displayNameProperty, dialogId, type } = this.props + const { displayNameProperty, dialogId, type: visType } = this.props const dimensionProps = { onSelect: this.selectUiItems, @@ -269,24 +274,29 @@ export class DialogManager extends Component { let infoBoxMessage const axisId = this.props.getAxisIdByDimensionId(dialogId) - const visType = type const numberOfItems = selectedItems.length + const dimensionMaxNumberOfItems = getDimensionMaxNumberOfItems( + visType, + dialogId + ) + const axisMaxNumberOfItems = getAxisMaxNumberOfItems( visType, axisId ) - const hasMaxNumberOfItemsRule = !!axisMaxNumberOfItems + const hasMaxNumberOfItemsRule = Boolean( + axisMaxNumberOfItems || dimensionMaxNumberOfItems + ) + const maxNumberOfItems = + axisMaxNumberOfItems || dimensionMaxNumberOfItems - if ( - hasMaxNumberOfItemsRule && - numberOfItems > axisMaxNumberOfItems - ) { + if (hasMaxNumberOfItemsRule && numberOfItems > maxNumberOfItems) { infoBoxMessage = - axisMaxNumberOfItems === 1 + maxNumberOfItems === 1 ? i18n.t( - `'{{visualizationType}}' is intended to show a single data item. Only the first item will be used and saved.`, + `'{{visualizationType}}' is intended to show a single item for this type of dimension. Only the first item will be used and saved.`, { visualizationType: getDisplayNameByVisType(visType), @@ -297,12 +307,12 @@ export class DialogManager extends Component { { visualiationType: getDisplayNameByVisType(visType), - maxNumber: axisMaxNumberOfItems, + maxNumber: maxNumberOfItems, } ) selectedItems.forEach((item, index) => { - item.isActive = index < axisMaxNumberOfItems + item.isActive = index < maxNumberOfItems }) } else if (isScatterAttribute(dialogId) && numberOfItems > 1) { infoBoxMessage = i18n.t( @@ -312,11 +322,22 @@ export class DialogManager extends Component { item.isActive = index < 1 }) } + let content = null if ( dialogId === DIMENSION_ID_DATA || isScatterAttribute(dialogId) ) { + const dataTypes = Object.values(dataTypeMap).filter( + ({ id }) => { + if (visType === VIS_TYPE_OUTLIER_TABLE) { + return id === DIMENSION_TYPE_DATA_ELEMENT + } + + return true + } + ) + const onSelect = isScatterAttribute(dialogId) ? (defaultProps) => this.selectUiItems({ @@ -334,13 +355,38 @@ export class DialogManager extends Component { }, }) } + + if (visType === VIS_TYPE_OUTLIER_TABLE) { + let showInfo = false + + selectedItems.forEach((item) => { + if ( + ![ + DIMENSION_TYPE_DATA_ELEMENT, + DIMENSION_TYPE_DATA_ELEMENT_OPERAND, + ].includes(item.type) + ) { + item.isActive = false + showInfo = true + } + }) + + if (showInfo) { + infoBoxMessage = i18n.t( + `'Outlier table' shows values from data elements only. Only data elements will be used and saved.` + ) + } + } + const dimensionSelector = ( ) const dataTabs = isScatterAttribute(dialogId) ? ( @@ -380,6 +426,7 @@ export class DialogManager extends Component { content = ( { DIMENSION_ID_DATA: dataId, DIMENSION_ID_PERIOD: periodId, DIMENSION_ID_ORGUNIT: ouId, + dataTypeMap: {}, getAxisMaxNumberOfItems: () => {}, + getDimensionMaxNumberOfItems: () => 1, filterOutPredefinedDimensions: () => [], getPredefinedDimensions: () => {}, getPredefinedDimensionProp: () => {}, diff --git a/src/components/DimensionsPanel/Dialogs/__tests__/__snapshots__/DialogManager.spec.js.snap b/src/components/DimensionsPanel/Dialogs/__tests__/__snapshots__/DialogManager.spec.js.snap index fc7ccfa69c..bfba0f2f6c 100644 --- a/src/components/DimensionsPanel/Dialogs/__tests__/__snapshots__/DialogManager.spec.js.snap +++ b/src/components/DimensionsPanel/Dialogs/__tests__/__snapshots__/DialogManager.spec.js.snap @@ -83,6 +83,7 @@ exports[`The DialogManager component renders OUDimension content with display:no /> + this.props.dimensions.filter(filter).filter(this.nameContainsFilterText) + isSelected = (id) => this.props.selectedIds.includes(id) - isDisabledDimension = (id) => this.props.disallowedDimensions.includes(id) + isDisabledDimension = (id) => + // all dimensions in YOUR DIMENSIONS section need to be disabled for Outlier table + this.props.visType === VIS_TYPE_OUTLIER_TABLE + ? this.props.disallowedDimensions + .concat(this.yourDimensions.map((d) => d.id)) + .includes(id) + : this.props.disallowedDimensions.includes(id) isLockedDimension = (id) => this.props.lockedDimensions.includes(id) isRecommendedDimension = (id) => this.props.recommendedIds.includes(id) @@ -51,25 +61,22 @@ export class DndDimensionList extends Component { ) } - getDimensionItemsByFilter = (filter) => - this.props.dimensions - .filter(filter) - .filter(this.nameContainsFilterText) - .map(this.renderItem) + getDimensionItemsFromList = (dimensionList) => + dimensionList.map(this.renderItem) render() { this.dndIndex = 0 - const fixedDimensions = this.getDimensionItemsByFilter((dimension) => + this.mainDimensions = this.getFilteredDimensions((dimension) => Object.values(getFixedDimensions()).some( (fixedDim) => fixedDim.id === dimension.id ) ) - const dynamicDimensions = this.getDimensionItemsByFilter((dimension) => + this.otherDimensions = this.getFilteredDimensions((dimension) => Object.values(getDynamicDimensions()).some( (dynDim) => dynDim.id === dimension.id ) ) - const nonPredefinedDimensions = this.getDimensionItemsByFilter( + this.yourDimensions = this.getFilteredDimensions( (dimension) => !Object.values(getPredefinedDimensions()).some( (predefDim) => predefDim.id === dimension.id @@ -94,7 +101,9 @@ export class DndDimensionList extends Component { className={styles.list} data-test={`${this.props.dataTest}-fixed-dimensions`} > - {fixedDimensions} + {this.getDimensionItemsFromList( + this.mainDimensions + )}
@@ -105,7 +114,9 @@ export class DndDimensionList extends Component { className={styles.list} data-test={`${this.props.dataTest}-dynamic-dimensions`} > - {dynamicDimensions} + {this.getDimensionItemsFromList( + this.otherDimensions + )}
@@ -116,7 +127,9 @@ export class DndDimensionList extends Component { className={styles.list} data-test={`${this.props.dataTest}-non-predefined-dimensions`} > - {nonPredefinedDimensions} + {this.getDimensionItemsFromList( + this.yourDimensions + )}
@@ -136,6 +149,7 @@ DndDimensionList.propTypes = { lockedDimensions: PropTypes.array, recommendedIds: PropTypes.array, selectedIds: PropTypes.array, + visType: PropTypes.string, onDimensionClick: PropTypes.func, onDimensionOptionsClick: PropTypes.func, } @@ -158,6 +172,7 @@ const mapStateToProps = (state) => ({ recommendedIds: fromReducers.fromRecommendedIds.sGetRecommendedIds(state), disallowedDimensions: getDisallowedDimensionsMemo(state), lockedDimensions: getisLockedDimensionsMemo(state), + visType: fromReducers.fromUi.sGetUiType(state), }) export default connect(mapStateToProps)(DndDimensionList) diff --git a/src/components/DownloadMenu/AdvancedSubMenu.js b/src/components/DownloadMenu/AdvancedSubMenu.js index 0f536d3aaf..80f6b1dbb5 100644 --- a/src/components/DownloadMenu/AdvancedSubMenu.js +++ b/src/components/DownloadMenu/AdvancedSubMenu.js @@ -24,6 +24,7 @@ export const AdvancedSubMenu = ({ {visType === VIS_TYPE_PIVOT_TABLE ? ( - ) : ( + ) : visType !== VIS_TYPE_OUTLIER_TABLE ? ( - )} + ) : null} - + {visType !== VIS_TYPE_OUTLIER_TABLE && ( + + )} - + {visType !== VIS_TYPE_OUTLIER_TABLE && ( + + )} ) } diff --git a/src/components/DownloadMenu/useDownload.js b/src/components/DownloadMenu/useDownload.js index 07a32c8357..6776a08c17 100644 --- a/src/components/DownloadMenu/useDownload.js +++ b/src/components/DownloadMenu/useDownload.js @@ -1,9 +1,11 @@ -import { Analytics } from '@dhis2/analytics' +import { Analytics, VIS_TYPE_OUTLIER_TABLE } from '@dhis2/analytics' import { useConfig, useDataEngine, useDataMutation } from '@dhis2/app-runtime' import { useCallback } from 'react' import { useSelector } from 'react-redux' +import { getAnalyticsRequestForOutlierTable } from '../../api/analytics.js' import { sGetChart } from '../../reducers/chart.js' import { sGetCurrent } from '../../reducers/current.js' +import { sGetSettingsDisplayProperty } from '../../reducers/settings.js' import { sGetUiType, sGetUiLayoutColumns, @@ -54,6 +56,7 @@ const addCommonParameters = (req, visualization, options) => { } const useDownload = (relativePeriodDate) => { + const displayProperty = useSelector(sGetSettingsDisplayProperty) const visualization = useSelector(sGetCurrent) const visType = useSelector(sGetUiType) const chart = useSelector(sGetChart) @@ -106,62 +109,96 @@ const useDownload = (relativePeriodDate) => { let req = new analyticsEngine.request() let target = '_top' - switch (type) { - case DOWNLOAD_TYPE_TABLE: - req = req - .fromVisualization(visualization) - .withFormat(format) - .withTableLayout() - .withColumns(columns.join(';')) - .withRows(rows.join(';')) - - req = addCommonParameters(req, visualization, { - relativePeriodDate, - }) - - if (visualization.hideEmptyColumns) { - req = req.withHideEmptyColumns() - } - - if (visualization.hideEmptyRows) { - req = req.withHideEmptyRows() - } - - if (visualization.showHierarchy) { - req = req.withShowHierarchy() - } - - target = format === FILE_FORMAT_HTML_CSS ? '_blank' : '_top' - - break - case DOWNLOAD_TYPE_PLAIN: - req = req - .fromVisualization( - visualization, - path === 'dataValueSet' + if (visType === VIS_TYPE_OUTLIER_TABLE) { + // only DOWNLOAD_TYPE_PLAIN is enabled + // open JSON in new tab + target = [FILE_FORMAT_CSV, FILE_FORMAT_XLS].includes(format) + ? '_top' + : '_blank' + + req = getAnalyticsRequestForOutlierTable({ + analyticsEngine, + visualization, + options: { + showHierarchy: visualization.showHierarchy, + skipRounding: visualization.skipRounding, + }, + forDownload: true, + }) + + if (relativePeriodDate) { + req = req.withRelativePeriodDate(relativePeriodDate) + } + + if (displayProperty) { + req = req.withDisplayProperty(displayProperty) + } + + req = req + .withFormat(format) + .withOutputIdScheme(idScheme) + .withPath('outlierDetection') + } else { + switch (type) { + case DOWNLOAD_TYPE_TABLE: + req = req + .fromVisualization(visualization) + .withFormat(format) + .withTableLayout() + .withColumns(columns.join(';')) + .withRows(rows.join(';')) + + req = addCommonParameters(req, visualization, { + relativePeriodDate, + }) + + if (visualization.hideEmptyColumns) { + req = req.withHideEmptyColumns() + } + + if (visualization.hideEmptyRows) { + req = req.withHideEmptyRows() + } + + if (visualization.showHierarchy) { + req = req.withShowHierarchy() + } + + target = + format === FILE_FORMAT_HTML_CSS ? '_blank' : '_top' + + break + case DOWNLOAD_TYPE_PLAIN: + req = req + .fromVisualization( + visualization, + path === 'dataValueSet' + ) + .withFormat(format) + .withShowHierarchy(visualization.showHierarchy) + .withHierarchyMeta(visualization.showHierarchy) + .withIncludeMetadataDetails(true) + .withIncludeNumDen() + + req = addCommonParameters(req, visualization, { + relativePeriodDate, + }) + + if (path) { + req = req.withPath(path) + } + + if (idScheme) { + req = req.withOutputIdScheme(idScheme) + } + + target = [FILE_FORMAT_CSV, FILE_FORMAT_XLS].includes( + format ) - .withFormat(format) - .withShowHierarchy(visualization.showHierarchy) - .withHierarchyMeta(visualization.showHierarchy) - .withIncludeMetadataDetails(true) - .withIncludeNumDen() - - req = addCommonParameters(req, visualization, { - relativePeriodDate, - }) - - if (path) { - req = req.withPath(path) - } - - if (idScheme) { - req = req.withOutputIdScheme(idScheme) - } - - target = [FILE_FORMAT_CSV, FILE_FORMAT_XLS].includes(format) - ? '_top' - : '_blank' - break + ? '_top' + : '_blank' + break + } } const url = new URL( @@ -179,9 +216,11 @@ const useDownload = (relativePeriodDate) => { analyticsEngine, baseUrl, columns, + displayProperty, relativePeriodDate, rows, visualization, + visType, ] ) diff --git a/src/components/InterpretationModal/InterpretationModal.js b/src/components/InterpretationModal/InterpretationModal.js index 71b61d709b..8a60ca4965 100644 --- a/src/components/InterpretationModal/InterpretationModal.js +++ b/src/components/InterpretationModal/InterpretationModal.js @@ -4,7 +4,7 @@ import React, { useState, useEffect } from 'react' import { useSelector } from 'react-redux' import { sGetCurrent } from '../../reducers/current.js' import { ModalDownloadDropdown } from '../DownloadMenu/index.js' -import { VisualizationPlugin } from '../VisualizationPlugin/VisualizationPlugin.js' +import { VisualizationPluginWrapper } from '../VisualizationPlugin/VisualizationPluginWrapper.js' import { useInterpretationQueryParams, removeInterpretationQueryParams, @@ -30,7 +30,7 @@ const InterpretationModal = ({ onInterpretationUpdate }, context) => { onResponsesReceived={() => setIsVisualizationLoading(false)} visualization={visualization} downloadMenuComponent={ModalDownloadDropdown} - pluginComponent={VisualizationPlugin} + pluginComponent={VisualizationPluginWrapper} /> ) : null } diff --git a/src/components/Layout/Chip.js b/src/components/Layout/Chip.js index 6f4aade23b..411dca6523 100644 --- a/src/components/Layout/Chip.js +++ b/src/components/Layout/Chip.js @@ -1,13 +1,18 @@ // TODO: Refactor chip to contain less logic import { getPredefinedDimensionProp, + getDimensionMaxNumberOfItems, getAxisMaxNumberOfItems, hasAxisTooManyItems, + hasDimensionTooManyItems, getDisplayNameByVisType, getAxisNameByLayoutType, getLayoutTypeByVisType, DIMENSION_ID_ASSIGNED_CATEGORIES, DIMENSION_PROP_NO_ITEMS, + DIMENSION_TYPE_DATA_ELEMENT, + DIMENSION_TYPE_DATA_ELEMENT_OPERAND, + VIS_TYPE_OUTLIER_TABLE, VIS_TYPE_SCATTER, DIMENSION_ID_DATA, ALL_DYNAMIC_DIMENSION_ITEMS, @@ -59,11 +64,32 @@ const Chip = ({ ) + const getMaxNumberOfItems = () => + getAxisMaxNumberOfItems(type, axisId) || + getDimensionMaxNumberOfItems(type, dimensionId) + + let activeItemIds = getMaxNumberOfItems() + ? items.slice(0, getMaxNumberOfItems()) + : items + + // filter out non DATA_ELEMENT types for Outlier table vis type + if (type === VIS_TYPE_OUTLIER_TABLE && dimensionId === DIMENSION_ID_DATA) { + activeItemIds = activeItemIds.filter((id) => + [ + DIMENSION_TYPE_DATA_ELEMENT, + DIMENSION_TYPE_DATA_ELEMENT_OPERAND, + ].includes(metadata[id]?.dimensionItemType) + ) + } + + const hasWarning = + hasAxisTooManyItems(type, axisId, items.length) || + hasDimensionTooManyItems(type, dimensionId, items.length) || + items.length > activeItemIds.length + const isSplitAxis = type === VIS_TYPE_SCATTER && dimensionId === DIMENSION_ID_DATA - const getMaxNumberOfItems = () => getAxisMaxNumberOfItems(type, axisId) - const handleClick = () => { if (!getPredefinedDimensionProp(dimensionId, DIMENSION_PROP_NO_ITEMS)) { onClick() @@ -120,9 +146,6 @@ const Chip = ({ } const renderTooltipContent = () => { - const activeItemIds = getMaxNumberOfItems() - ? items.slice(0, getMaxNumberOfItems()) - : items const lockedLabel = isLocked ? i18n.t( `{{dimensionName}} is locked to {{axisName}} for {{visTypeName}}`, @@ -158,8 +181,7 @@ const Chip = ({ {renderChipLabelSuffix()} - {hasAxisTooManyItems(type, axisId, items.length) && - WarningIconWrapper} + {hasWarning && WarningIconWrapper} {isLocked && LockIconWrapper} ) diff --git a/src/components/Layout/Layout.js b/src/components/Layout/Layout.js index dc9793b30c..d830d24126 100644 --- a/src/components/Layout/Layout.js +++ b/src/components/Layout/Layout.js @@ -3,14 +3,16 @@ import { LAYOUT_TYPE_PIE, LAYOUT_TYPE_YEAR_OVER_YEAR, LAYOUT_TYPE_PIVOT_TABLE, - getLayoutTypeByVisType, LAYOUT_TYPE_SCATTER, + LAYOUT_TYPE_OUTLIER_TABLE, + getLayoutTypeByVisType, } from '@dhis2/analytics' import PropTypes from 'prop-types' import React from 'react' import { connect } from 'react-redux' import { sGetUiType } from '../../reducers/ui.js' import DefaultLayout from './DefaultLayout/DefaultLayout.js' +import OutlierTableLayout from './OutlierTable/OutlierTableLayout.js' import PieLayout from './PieLayout/PieLayout.js' import PivotTableLayout from './PivotTableLayout/PivotTableLayout.js' import ScatterLayout from './ScatterLayout/ScatterLayout.js' @@ -22,6 +24,7 @@ const componentMap = { [LAYOUT_TYPE_YEAR_OVER_YEAR]: YearOverYearLayout, [LAYOUT_TYPE_PIVOT_TABLE]: PivotTableLayout, [LAYOUT_TYPE_SCATTER]: ScatterLayout, + [LAYOUT_TYPE_OUTLIER_TABLE]: OutlierTableLayout, } const Layout = ({ visType }) => { diff --git a/src/components/Layout/OutlierTable/OutlierTableLayout.js b/src/components/Layout/OutlierTable/OutlierTableLayout.js new file mode 100644 index 0000000000..ff079ef825 --- /dev/null +++ b/src/components/Layout/OutlierTable/OutlierTableLayout.js @@ -0,0 +1,30 @@ +import { AXIS_ID_COLUMNS } from '@dhis2/analytics' +import React from 'react' +import DefaultAxis from '../DefaultLayout/DefaultAxis.js' +import defaultAxisStyles from '../DefaultLayout/styles/DefaultAxis.style.js' +import defaultLayoutStyles from '../DefaultLayout/styles/DefaultLayout.style.js' +import outlierTableLayoutStyles from './styles/OutlierTableLayout.style.js' + +const Layout = () => ( +
+
+ +
+
+) + +Layout.displayName = 'Layout' + +export default Layout diff --git a/src/components/Layout/OutlierTable/styles/OutlierTableLayout.style.js b/src/components/Layout/OutlierTable/styles/OutlierTableLayout.style.js new file mode 100644 index 0000000000..c8c3cbabf5 --- /dev/null +++ b/src/components/Layout/OutlierTable/styles/OutlierTableLayout.style.js @@ -0,0 +1,5 @@ +export default { + axisGroupLeft: { + flexBasis: '100%', + }, +} diff --git a/src/components/Layout/TooltipContent.js b/src/components/Layout/TooltipContent.js index f1e2ed7b32..150a591966 100644 --- a/src/components/Layout/TooltipContent.js +++ b/src/components/Layout/TooltipContent.js @@ -9,9 +9,10 @@ import { styles } from './styles/Tooltip.style.js' const labels = { noneSelected: () => i18n.t('None selected'), + noneInUse: () => i18n.t('None in use'), onlyOneInUse: (name) => i18n.t("Only '{{- name}}' in use", { name }), onlyLimitedNumberInUse: (number) => - i18n.t("Only '{{number}}' in use", { number }), + i18n.t('Only {{number}} in use', { number }), allItems: () => i18n.t('All items are selected'), } @@ -25,7 +26,9 @@ export const TooltipContent = ({ const hasAllItemsSelected = itemIds.includes(ALL_DYNAMIC_DIMENSION_ITEMS) const getWarningLabel = () => { const warningLabel = - itemIds.length === 1 + itemIds.length === 0 + ? labels.noneInUse() + : itemIds.length === 1 ? labels.onlyOneInUse( metadata[itemIds[0]] ? metadata[itemIds[0]].name diff --git a/src/components/MenuBar/MenuBar.js b/src/components/MenuBar/MenuBar.js index f6150c33cb..add7d03de8 100644 --- a/src/components/MenuBar/MenuBar.js +++ b/src/components/MenuBar/MenuBar.js @@ -15,6 +15,7 @@ import history from '../../modules/history.js' import { visTypes, getVisualizationState, + useVisTypesFilterByVersion, STATE_UNSAVED, STATE_DIRTY, } from '../../modules/visualization.js' @@ -48,55 +49,59 @@ const getOnSaveAs = (props) => (details) => const getOnDelete = (props) => () => props.onDeleteVisualization() const getOnError = (props) => (error) => props.onError(error) -const filterVisTypes = [ - { type: VIS_TYPE_GROUP_ALL }, - { type: VIS_TYPE_GROUP_CHARTS, insertDivider: true }, - ...visTypes.map((visType) => ({ - type: visType, - })), -] +const UnconnectedMenuBar = ({ dataTest, ...props }, context) => { + const filterVisTypesByVersion = useVisTypesFilterByVersion() -const UnconnectedMenuBar = ({ dataTest, ...props }, context) => ( - <> - ( - - )} - /> - - ({ + type: visType, + })), + ] + + return ( + <> + ( + + )} /> - + + + - - - - -) + + + + + ) +} UnconnectedMenuBar.propTypes = { apiObjectName: PropTypes.string, diff --git a/src/components/Visualization/Visualization.js b/src/components/Visualization/Visualization.js index c619c23760..f25e90ee08 100644 --- a/src/components/Visualization/Visualization.js +++ b/src/components/Visualization/Visualization.js @@ -1,4 +1,8 @@ -import { DIMENSION_ID_DATA, VIS_TYPE_PIVOT_TABLE } from '@dhis2/analytics' +import { + DIMENSION_ID_DATA, + VIS_TYPE_OUTLIER_TABLE, + VIS_TYPE_PIVOT_TABLE, +} from '@dhis2/analytics' import debounce from 'lodash-es/debounce' import PropTypes from 'prop-types' import React, { Component, Fragment } from 'react' @@ -7,7 +11,11 @@ import { acSetChart } from '../../actions/chart.js' import { tSetCurrentFromUi } from '../../actions/current.js' import { acSetLoadError, acSetPluginLoading } from '../../actions/loader.js' import { acAddMetadata } from '../../actions/metadata.js' -import { acSetUiItems, acAddParentGraphMap } from '../../actions/ui.js' +import { + acSetUiItems, + acSetUiDataSorting, + acAddParentGraphMap, +} from '../../actions/ui.js' import { AssignedCategoriesDataElementsError, GenericServerError, @@ -21,6 +29,7 @@ import { ValueTypeError, AnalyticsGenerationError, AnalyticsRequestError, + NoOutliersError, } from '../../modules/error.js' import { removeLastPathSegment } from '../../modules/orgUnit.js' import { sGetCurrent } from '../../reducers/current.js' @@ -82,6 +91,9 @@ export class UnconnectedVisualization extends Component { case 'E7145': error = new AnalyticsRequestError() break + case 'E2200': + error = new NoDataError(this.props.visualization.type) + break default: error = response } @@ -94,6 +106,15 @@ export class UnconnectedVisualization extends Component { onChartGenerated = (svg) => this.props.setChart(svg) + onDataSorted = (sorting) => { + this.props.onLoadingStart() + + this.props.setUiDataSorting(sorting) + + // simulate an update for refreshing the visualization + this.props.setCurrent() + } + onResponsesReceived = (responses) => { const forMetadata = {} @@ -109,14 +130,16 @@ export class UnconnectedVisualization extends Component { throw new ValueTypeError() } - Object.entries(response.metaData.items).forEach(([id, item]) => { - forMetadata[id] = { - id, - name: item.name || item.displayName, - displayName: item.displayName, - dimensionItemType: item.dimensionItemType, + Object.entries(response?.metaData?.items || []).forEach( + ([id, item]) => { + forMetadata[id] = { + id, + name: item.name || item.displayName, + displayName: item.displayName, + dimensionItemType: item.dimensionItemType, + } } - }) + ) }) this.props.addMetadata(forMetadata) @@ -124,6 +147,10 @@ export class UnconnectedVisualization extends Component { if ( !responses.some((response) => response.rows && response.rows.length) ) { + if (this.props.visualization.type === VIS_TYPE_OUTLIER_TABLE) { + throw new NoOutliersError() + } + throw new EmptyResponseError() } } @@ -211,6 +238,7 @@ export class UnconnectedVisualization extends Component { visualization={visualization} onChartGenerated={this.onChartGenerated} onLoadingComplete={onLoadingComplete} + onDataSorted={this.onDataSorted} onResponsesReceived={this.onResponsesReceived} onError={this.onError} onDrill={this.onDrill} @@ -232,9 +260,11 @@ UnconnectedVisualization.propTypes = { setChart: PropTypes.func, setCurrent: PropTypes.func, setLoadError: PropTypes.func, + setUiDataSorting: PropTypes.func, setUiItems: PropTypes.func, visualization: PropTypes.object, onLoadingComplete: PropTypes.func, + onLoadingStart: PropTypes.func, } const mapStateToProps = (state) => ({ @@ -247,12 +277,14 @@ const mapStateToProps = (state) => ({ const mapDispatchToProps = (dispatch) => ({ onLoadingComplete: () => dispatch(acSetPluginLoading(false)), + onLoadingStart: () => dispatch(acSetPluginLoading(true)), addMetadata: (metadata) => dispatch(acAddMetadata(metadata)), addParentGraphMap: (parentGraphMap) => dispatch(acAddParentGraphMap(parentGraphMap)), setChart: (chart) => dispatch(acSetChart(chart)), setLoadError: (error) => dispatch(acSetLoadError(error)), setUiItems: (data) => dispatch(acSetUiItems(data)), + setUiDataSorting: (sorting) => dispatch(acSetUiDataSorting(sorting)), setCurrent: () => dispatch(tSetCurrentFromUi()), }) diff --git a/src/components/VisualizationOptions/Options/OutlierDetectionMethod.js b/src/components/VisualizationOptions/Options/OutlierDetectionMethod.js index 1055781ce9..4f7cb0f951 100644 --- a/src/components/VisualizationOptions/Options/OutlierDetectionMethod.js +++ b/src/components/VisualizationOptions/Options/OutlierDetectionMethod.js @@ -38,7 +38,7 @@ const OutlierDetectionMethod = ({ label={i18n.t('Threshold factor')} min="0" step="0.5" - onChange={({ value }) => onThresholdChange(Number(value))} + onChange={({ value }) => onThresholdChange(value)} value={currentThreshold?.toString() || ''} helpText={i18n.t( 'A high value is more sensitive so fewer data items will be identified as outliers' diff --git a/src/components/VisualizationOptions/Options/Outliers.js b/src/components/VisualizationOptions/Options/Outliers.js index dca437745c..b670213d8e 100644 --- a/src/components/VisualizationOptions/Options/Outliers.js +++ b/src/components/VisualizationOptions/Options/Outliers.js @@ -56,6 +56,9 @@ const DEFAULT_STATE = { const dataTest = 'option-outliers' const Outliers = ({ outlierAnalysis, onChange }) => { + // initialise extremeLines if empty (ie. option saved in Outlier table) + outlierAnalysis.extremeLines ??= DEFAULT_STATE.extremeLines + const storeProp = (prop, value) => onChange({ ...outlierAnalysis, [prop]: value }) diff --git a/src/components/VisualizationOptions/Options/OutliersForOutlierTable.js b/src/components/VisualizationOptions/Options/OutliersForOutlierTable.js new file mode 100644 index 0000000000..26142b205a --- /dev/null +++ b/src/components/VisualizationOptions/Options/OutliersForOutlierTable.js @@ -0,0 +1,123 @@ +import i18n from '@dhis2/d2-i18n' +import { FieldSet, Legend } from '@dhis2/ui' +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { acSetUiDataSorting, acSetUiOptions } from '../../../actions/ui.js' +import { getDefaultSorting } from '../../../modules/ui.js' +import { sGetUi, sGetUiOptions } from '../../../reducers/ui.js' +import { + tabSectionOption, + tabSectionTitle, +} from '../styles/VisualizationOptions.style.js' +import OutlierDetectionMethod from './OutlierDetectionMethod.js' +import { DEFAULT_STATE as OUTLIER_MAX_RESULTS_DEFAULT_STATE } from './OutliersMaxResults.js' + +export const OUTLIER_METHOD_PROP = 'outlierMethod' +export const OUTLIER_THRESHOLD_PROP = 'thresholdFactor' + +export const OUTLIER_ANALYSIS_OPTION_NAME = 'outlierAnalysis' + +export const METHOD_STANDARD_Z_SCORE = 'STANDARD_Z_SCORE' +export const METHOD_MODIFIED_Z_SCORE = 'MODIFIED_Z_SCORE' +const methods = [ + { + id: METHOD_STANDARD_Z_SCORE, + label: i18n.t('Z-score / Standard score'), + defaultThreshold: 3, + }, + { + id: METHOD_MODIFIED_Z_SCORE, + label: i18n.t('Modified Z-score'), + defaultThreshold: 3, + }, +] + +export const DEFAULT_STATE = { + [OUTLIER_METHOD_PROP]: METHOD_MODIFIED_Z_SCORE, + [OUTLIER_THRESHOLD_PROP]: 3, +} + +const Outliers = () => { + const dispatch = useDispatch() + + let outlierAnalysis = + useSelector(sGetUiOptions)[OUTLIER_ANALYSIS_OPTION_NAME] + + if ( + !outlierAnalysis || + !methods + .map(({ id }) => id) + .includes(outlierAnalysis[OUTLIER_METHOD_PROP]) + ) { + outlierAnalysis = { + ...OUTLIER_MAX_RESULTS_DEFAULT_STATE, + ...DEFAULT_STATE, + } + + dispatch( + acSetUiOptions({ + [OUTLIER_ANALYSIS_OPTION_NAME]: outlierAnalysis, + }) + ) + } + + const sorting = useSelector(sGetUi).sorting + + const onMethodChange = (value) => { + dispatch( + acSetUiOptions({ + [OUTLIER_ANALYSIS_OPTION_NAME]: { + ...outlierAnalysis, + [OUTLIER_METHOD_PROP]: value, + [OUTLIER_THRESHOLD_PROP]: methods.find( + (item) => item.id === value + ).defaultThreshold, + }, + }) + ) + + // reset sorting to avoid sorting on non existing column + // in case the sorting was on a column specific to a method + if ( + sorting?.dimension && + !['value', 'lowerbound', 'upperbound'].includes(sorting.dimension) + ) { + dispatch(acSetUiDataSorting(getDefaultSorting())) + } + } + + const onThresholdChange = (value) => + dispatch( + acSetUiOptions({ + [OUTLIER_ANALYSIS_OPTION_NAME]: { + ...outlierAnalysis, + [OUTLIER_THRESHOLD_PROP]: value, + }, + }) + ) + + return ( +
+
+ + + {i18n.t('Outlier detection method')} + + +
+ +
+
+
+ ) +} + +export default Outliers diff --git a/src/components/VisualizationOptions/Options/OutliersMaxResults.js b/src/components/VisualizationOptions/Options/OutliersMaxResults.js new file mode 100644 index 0000000000..6f89afe1ae --- /dev/null +++ b/src/components/VisualizationOptions/Options/OutliersMaxResults.js @@ -0,0 +1,72 @@ +import i18n from '@dhis2/d2-i18n' +import { InputField } from '@dhis2/ui' +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { acSetUiOptions } from '../../../actions/ui.js' +import { sGetUiOptions } from '../../../reducers/ui.js' +import { tabSectionOption } from '../styles/VisualizationOptions.style.js' +import { DEFAULT_STATE as OUTLIER_METHOD_THRESHOLD_DEFAULT_STATE } from './OutliersForOutlierTable.js' + +const OUTLIER_ANALYSIS_OPTION_NAME = 'outlierAnalysis' +const MIN_VALUE = 1 +const MAX_VALUE = 500 + +export const OUTLIER_MAX_RESULTS_PROP = 'maxResults' +export const DEFAULT_STATE = { + [OUTLIER_MAX_RESULTS_PROP]: 20, +} + +const OutliersMaxResults = () => { + const dispatch = useDispatch() + + const outlierAnalysis = useSelector(sGetUiOptions)[ + OUTLIER_ANALYSIS_OPTION_NAME + ] || { + ...OUTLIER_METHOD_THRESHOLD_DEFAULT_STATE, + ...DEFAULT_STATE, + } + + const onChange = ({ value }) => { + const parsedValue = parseInt(value, 10) + + dispatch( + acSetUiOptions({ + [OUTLIER_ANALYSIS_OPTION_NAME]: { + ...outlierAnalysis, + [OUTLIER_MAX_RESULTS_PROP]: + parsedValue > MAX_VALUE + ? MAX_VALUE + : parsedValue < MIN_VALUE + ? MIN_VALUE + : Math.round(parsedValue), + }, + }) + ) + } + + return ( +
+
+ +
+
+ ) +} + +export default OutliersMaxResults diff --git a/src/components/VisualizationPlugin/OutlierTablePlugin.js b/src/components/VisualizationPlugin/OutlierTablePlugin.js new file mode 100644 index 0000000000..ee94031ecf --- /dev/null +++ b/src/components/VisualizationPlugin/OutlierTablePlugin.js @@ -0,0 +1,299 @@ +import { + formatValue, + VALUE_TYPE_NUMBER, + VALUE_TYPE_INTEGER, + VALUE_TYPE_INTEGER_POSITIVE, + VALUE_TYPE_INTEGER_NEGATIVE, + VALUE_TYPE_INTEGER_ZERO_OR_POSITIVE, + VALUE_TYPE_PERCENTAGE, + VALUE_TYPE_UNIT_INTERVAL, + VALUE_TYPE_TIME, + VALUE_TYPE_DATE, + VALUE_TYPE_DATETIME, + VALUE_TYPE_TEXT, +} from '@dhis2/analytics' +import i18n from '@dhis2/d2-i18n' +import { + DataTable, + DataTableBody, + DataTableCell, + DataTableColumnHeader, + DataTableHead, + DataTableRow, + Tooltip, +} from '@dhis2/ui' +import cx from 'classnames' +import PropTypes from 'prop-types' +import React, { useCallback, useMemo, useRef, useState } from 'react' +import { getOutlierTableHeadersDetails } from '../../modules/analytics.js' +import { + DISPLAY_DENSITY_COMFORTABLE, + DISPLAY_DENSITY_COMPACT, + FONT_SIZE_LARGE, + FONT_SIZE_NORMAL, + FONT_SIZE_SMALL, +} from '../../modules/options.js' +import { + getDefaultSorting, + getSortingFromVisualization, +} from '../../modules/ui.js' +import styles from './styles/OutlierTablePlugin.module.css' + +const getFontSizeClass = (fontSize) => { + switch (fontSize) { + case FONT_SIZE_LARGE: + return styles.fontSizeLarge + case FONT_SIZE_SMALL: + return styles.fontSizeSmall + case FONT_SIZE_NORMAL: + default: + return styles.fontSizeNormal + } +} + +const getSizeClass = (displayDensity) => { + switch (displayDensity) { + case DISPLAY_DENSITY_COMFORTABLE: + return styles.sizeComfortable + case DISPLAY_DENSITY_COMPACT: + return styles.sizeCompact + default: + return styles.sizeNormal + } +} + +const OutlierTablePlugin = ({ + filters, + responses, + visualization, + onDataSorted, +}) => { + const data = responses[0] + const headersDetails = getOutlierTableHeadersDetails({ + showHierarchy: visualization.showHierarchy, + }) + + const containerRef = useRef(null) + const [measuredDimensions, setMeasuredDimensions] = useState({ + containerMaxWidth: 0, + paginationMaxWidth: 0, + noticeBoxMaxWidth: 0, + }) + + const defaultSorting = useMemo(() => getDefaultSorting(), []) + const getSorting = useCallback( + (visualization) => { + const sorting = + getSortingFromVisualization(visualization) || defaultSorting + + return { + sortField: sorting.dimension, + sortDirection: sorting.direction, + } + }, + [defaultSorting] + ) + + const { sortField, sortDirection } = getSorting(visualization) + + const sizeClass = getSizeClass(visualization.displayDensity) + const fontSizeClass = getFontSizeClass(visualization.fontSize) + + const isInModal = !!filters?.relativePeriodDate + + const onResize = useCallback(() => { + if (!containerRef?.current || containerRef.current.clientWidth === 0) { + return + } + const containerInnerWidth = containerRef.current.clientWidth + const scrollBox = containerRef.current.querySelector('.tablescrollbox') + const scrollbarWidth = scrollBox.offsetWidth - scrollBox.clientWidth + const containerMaxWidth = containerInnerWidth - scrollbarWidth + + setMeasuredDimensions({ + containerMaxWidth, + paginationMaxWidth: containerMaxWidth - scrollbarWidth, + noticeBoxMaxWidth: scrollBox.offsetWidth, + }) + }, []) + + const sizeObserver = useMemo( + () => new window.ResizeObserver(onResize), + [onResize] + ) + + const mountAndObserveContainerRef = useCallback( + (node) => { + if (node === null) { + return + } + + containerRef.current = node + sizeObserver.observe(node) + + return sizeObserver.disconnect + }, + [sizeObserver] + ) + + const getDataTableScrollHeight = (isInModal) => + isInModal ? 'calc(100vh - 285px)' : '100%' + + const cellValueShouldNotWrap = (header) => + [ + VALUE_TYPE_NUMBER, + VALUE_TYPE_INTEGER, + VALUE_TYPE_INTEGER_POSITIVE, + VALUE_TYPE_INTEGER_NEGATIVE, + VALUE_TYPE_INTEGER_ZERO_OR_POSITIVE, + VALUE_TYPE_PERCENTAGE, + VALUE_TYPE_UNIT_INTERVAL, + VALUE_TYPE_TIME, + VALUE_TYPE_DATE, + VALUE_TYPE_DATETIME, + ].includes(header.valueType) + + const renderHeaderCell = ({ name, valueType }) => { + const columnName = headersDetails[name]?.label + const tooltipContent = headersDetails[name]?.tooltip + + return ( + + {tooltipContent ? ( + {columnName} + ) : ( + columnName + )} + + ) + } + + const formatValueCell = (value, header) => + formatValue( + value, + header.valueType || VALUE_TYPE_TEXT, + header.optionSet + ? {} + : { + digitGroupSeparator: visualization.digitGroupSeparator, + skipRounding: false, + } + ) + + const sortData = ({ name }) => { + const direction = + sortField === name + ? sortDirection === 'desc' + ? 'asc' + : 'desc' + : 'desc' + + onDataSorted({ dimension: name, direction }) + } + + return ( +
+
+ + + + {data.headers.map((header) => + renderHeaderCell(header) + )} + + + + {data.rows.map((row, rowIndex) => ( + + {row.map((value, columnIndex) => ( + + {formatValueCell( + value, + data.headers[columnIndex] + )} + + ))} + + ))} + + +
+
+ ) +} + +OutlierTablePlugin.defaultProps = { + style: {}, + onDataSorted: Function.prototype, +} + +OutlierTablePlugin.propTypes = { + responses: PropTypes.arrayOf(PropTypes.object).isRequired, + visualization: PropTypes.object.isRequired, + filters: PropTypes.object, + onDataSorted: PropTypes.func, +} + +export default OutlierTablePlugin diff --git a/src/components/VisualizationPlugin/VisualizationPlugin.js b/src/components/VisualizationPlugin/VisualizationPlugin.js index 5875e674ae..0ae9c965fa 100644 --- a/src/components/VisualizationPlugin/VisualizationPlugin.js +++ b/src/components/VisualizationPlugin/VisualizationPlugin.js @@ -1,4 +1,5 @@ import { + VIS_TYPE_OUTLIER_TABLE, VIS_TYPE_PIVOT_TABLE, apiFetchOrganisationUnitLevels, convertOuLevelsToUids, @@ -22,6 +23,7 @@ import { fetchData } from '../../modules/fetchData.js' import { getOptionsFromVisualization } from '../../modules/options.js' import ChartPlugin from './ChartPlugin.js' import ContextualMenu from './ContextualMenu.js' +import OutlierTablePlugin from './OutlierTablePlugin.js' import PivotPlugin from './PivotPlugin.js' import styles from './styles/VisualizationPlugin.module.css' @@ -35,6 +37,7 @@ export const VisualizationPlugin = ({ onChartGenerated, onError, onLoadingComplete, + onDataSorted, onResponsesReceived, onDrill, }) => { @@ -195,7 +198,7 @@ export const VisualizationPlugin = ({ // multiple responses are only for YOY which does not support legends or custom icon // safe to use only the 1st // dx dimensions might not be present, the empty array covers that case - const dxIds = responses[0].metaData.dimensions.dx || [] + const dxIds = responses[0].metaData.dimensions?.dx || [] // DHIS2-10496: show icon on the side of the single value if an icon is assigned in Maintenance app and // the "Show data item icon" option is set in DV options @@ -331,12 +334,16 @@ export const VisualizationPlugin = ({ } : style - // force height when no value available otherwise the PivotTable container sets 0 as height hiding the table content + // force wdth and height when no value available otherwise the PivotTable container sets 0 as height hiding the table content // and Highcharts does not render correctly the chart/legend if (!transformedStyle.height) { transformedStyle.height = size.height || '100%' } + if (!transformedStyle.width) { + transformedStyle.width = size.width || '100%' + } + const getLegendKey = () => { if (hasLegendSet && forDashboard) { return ( @@ -384,42 +391,64 @@ export const VisualizationPlugin = ({ } } + const renderPlugin = () => { + if ( + !fetchResult.visualization.type || + fetchResult.visualization.type === VIS_TYPE_PIVOT_TABLE + ) { + return ( + + ) + } else if (fetchResult.visualization.type === VIS_TYPE_OUTLIER_TABLE) { + return ( + + ) + } else { + return ( + + ) + } + } + return (
-
- {!fetchResult.visualization.type || - fetchResult.visualization.type === VIS_TYPE_PIVOT_TABLE ? ( - - ) : ( - - )} -
+
{renderPlugin()}
{getLegendKey()} {contextualMenuRect && ( { + const [pluginProps, setPluginProps] = useState(props) + const [isLoading, setIsLoading] = useState(false) + + const onDataSorted = useCallback( + (sorting) => { + setIsLoading(true) + + const newSorting = { + dimension: sorting.dimension, + direction: sorting.direction.toUpperCase(), + } + + setPluginProps({ + ...pluginProps, + visualization: { + ...pluginProps.visualization, + sorting: [newSorting], + }, + }) + }, + [pluginProps] + ) + + useEffect(() => setPluginProps(props), [props]) + + const onLoadingComplete = () => setIsLoading(false) + + return ( + <> + {isLoading && ( + + + + + + )} + + + ) +} + +export { VisualizationPluginWrapper } diff --git a/src/components/VisualizationPlugin/styles/OutlierTablePlugin.module.css b/src/components/VisualizationPlugin/styles/OutlierTablePlugin.module.css new file mode 100644 index 0000000000..539d0aac6f --- /dev/null +++ b/src/components/VisualizationPlugin/styles/OutlierTablePlugin.module.css @@ -0,0 +1,64 @@ +.pluginContainer { + display: flex; + gap: var(--spacers-dp4); + min-width: 0; + width: 100%; + margin: 0 var(--spacers-dp4) var(--spacers-dp4); +} +.visualizationContainer { + display: flex; + flex-direction: column; + max-width: 100%; +} + +/* Table header cells in various sizes */ +.dataTable .headerCell.dimensionModalHandler { + cursor: pointer; +} +.dataTable .headerCell.fontSizeLarge { + font-size: 14px; + line-height: 15px; +} +.dataTable .headerCell.fontSizeNormal { + font-size: 12px; + line-height: 13px; +} +.dataTable .headerCell.fontSizeSmall { + font-size: 11px; + line-height: 12px; +} +.dataTable .headerCell.sizeComfortable { + padding: 8px 4px 6px 8px; +} +.dataTable .headerCell.sizeNormal { + padding: 4px 4px 2px 6px; +} +.dataTable .headerCell.sizeCompact { + padding: 3px 2px 1px 6px; +} + +/* Table body cells in various sizes */ +.dataTable .cell.fontSizeLarge { + font-size: 14px; + line-height: 15px; +} +.dataTable .cell.fontSizeNormal { + font-size: 12px; + line-height: 13px; +} +.dataTable .cell.fontSizeSmall { + font-size: 11px; + line-height: 12px; +} +.dataTable .cell.nowrap { + white-space: nowrap; +} +.dataTable .cell.sizeComfortable { + padding: 10px 8px 8px; +} +.dataTable .cell.sizeNormal { + padding: 7px 6px 5px; +} +.dataTable .cell.sizeCompact { + padding: 6px 6px 4px; +} diff --git a/src/components/VisualizationPlugin/styles/VisualizationPlugin.module.css b/src/components/VisualizationPlugin/styles/VisualizationPlugin.module.css index 4c3dee10e0..f03812c0e5 100644 --- a/src/components/VisualizationPlugin/styles/VisualizationPlugin.module.css +++ b/src/components/VisualizationPlugin/styles/VisualizationPlugin.module.css @@ -6,6 +6,7 @@ } .chartWrapper { + display: flex; flex-grow: 1; } diff --git a/src/components/VisualizationTypeSelector/ListItemIcon.js b/src/components/VisualizationTypeSelector/ListItemIcon.js index 7599c6f048..9fcc741b1e 100644 --- a/src/components/VisualizationTypeSelector/ListItemIcon.js +++ b/src/components/VisualizationTypeSelector/ListItemIcon.js @@ -14,6 +14,7 @@ import { VIS_TYPE_SINGLE_VALUE, VIS_TYPE_PIVOT_TABLE, VIS_TYPE_SCATTER, + VIS_TYPE_OUTLIER_TABLE, } from '@dhis2/analytics' import PropTypes from 'prop-types' import React from 'react' @@ -23,6 +24,7 @@ import ColumnIcon from '../../assets/ColumnIcon.js' import GaugeIcon from '../../assets/GaugeIcon.js' import GlobeIcon from '../../assets/GlobeIcon.js' import LineIcon from '../../assets/LineIcon.js' +import OutlierTableIcon from '../../assets/OutlierTableIcon.js' import PieIcon from '../../assets/PieIcon.js' import PivotTableIcon from '../../assets/PivotTableIcon.js' import RadarIcon from '../../assets/RadarIcon.js' @@ -66,6 +68,8 @@ const ListItemIcon = ({ iconType, style }) => { return case VIS_TYPE_SCATTER: return + case VIS_TYPE_OUTLIER_TABLE: + return case VIS_TYPE_COLUMN: default: return diff --git a/src/components/VisualizationTypeSelector/VisualizationTypeSelector.js b/src/components/VisualizationTypeSelector/VisualizationTypeSelector.js index 44c89e2aed..d91b3dc27c 100644 --- a/src/components/VisualizationTypeSelector/VisualizationTypeSelector.js +++ b/src/components/VisualizationTypeSelector/VisualizationTypeSelector.js @@ -16,6 +16,7 @@ import { getAdaptedUiByType } from '../../modules/ui.js' import { visTypes, getVisTypeDescriptions, + useVisTypesFilterByVersion, } from '../../modules/visualization.js' import { sGetCurrent } from '../../reducers/current.js' import { sGetMetadata } from '../../reducers/metadata.js' @@ -31,6 +32,7 @@ const UnconnectedVisualizationTypeSelector = ( context ) => { const baseUrl = context.baseUrl + const filterVisTypesByVersion = useVisTypesFilterByVersion() const [, /* actual value not used */ { set }] = useSetting( USER_DATASTORE_CURRENT_AO_KEY @@ -66,7 +68,7 @@ const UnconnectedVisualizationTypeSelector = (
- {visTypes.map((visType) => ( + {visTypes.filter(filterVisTypesByVersion).map((visType) => ( ({ + [DIMENSION_ID_DATA]: 'dxname', + [DIMENSION_ID_ORGUNIT]: showHierarchy ? 'ounamehierarchy' : 'ouname', + [DIMENSION_ID_PERIOD]: 'pename', +}) + +export const getOutlierTableHeadersDetails = () => { + const fixedDimensions = getFixedDimensions() + + return { + dxname: { + label: fixedDimensions[DIMENSION_ID_DATA]?.name, + }, + ouname: { + label: fixedDimensions[DIMENSION_ID_ORGUNIT]?.name, + }, + ounamehierarchy: { + label: fixedDimensions[DIMENSION_ID_ORGUNIT]?.name, + }, + pename: { + label: fixedDimensions[DIMENSION_ID_PERIOD]?.name, + }, + cocname: { + label: i18n.t('Category option combination'), + }, + value: { + label: i18n.t('Value'), + }, + absdev: { + label: i18n.t('Absolute deviation'), + tooltip: i18n.t( + 'A measure of the absolute difference between each data point and a central value, usually the mean or median, providing a straightforward understanding of dispersion in the dataset.' + ), + }, + modifiedzscore: { + label: i18n.t('Modified Z-score'), + tooltip: i18n.t( + 'A measure of how far a data point deviates from the median, using the median absolute deviation instead of the standard deviation, making it robust against outliers.' + ), + }, + median: { + label: i18n.t('Median'), + tooltip: i18n.t( + "The middle value in a dataset when the values are arranged in ascending or descending order. It's a robust measure of central tendency that is less affected by outliers compared to the mean." + ), + }, + medianabsdeviation: { + label: i18n.t('Median absolute deviation'), + tooltip: i18n.t( + "A robust measure of variability, found by calculating the median of the absolute differences between each data point and the overall median. It's less influenced by outliers compared to other measures like the standard deviation." + ), + }, + zscore: { + label: i18n.t('Z-score'), + tooltip: i18n.t( + 'A measure of how many standard deviations a data point is from the mean of a dataset, providing insight into how unusual or typical that data point is relative to the rest of the distribution.' + ), + }, + mean: { + label: i18n.t('Mean'), + tooltip: i18n.t('Average of the value over time.'), + }, + stddev: { + label: i18n.t('Standard deviation'), + tooltip: i18n.t( + 'A measure of how dispersed the data is in relation to the mean. Low standard deviation indicates data are clustered tightly around the mean, and high standard deviation indicates data are more spread out.' + ), + }, + lowerbound: { + label: i18n.t('Min'), + tooltip: i18n.t('Minimum score threshold'), + }, + upperbound: { + label: i18n.t('Max'), + tooltip: i18n.t('Maximum score threshold'), + }, + } +} export const computeYoYMatrix = (responses, relativePeriodTypeUsed) => { const periodGroups = responses.reduce((list, res) => { diff --git a/src/modules/current.js b/src/modules/current.js index 96c597a168..710ae837c9 100644 --- a/src/modules/current.js +++ b/src/modules/current.js @@ -6,6 +6,9 @@ import { AXIS_ID_FILTERS, DIMENSION_ID_DATA, DIMENSION_ID_PERIOD, + DIMENSION_TYPE_DATA_ELEMENT, + DIMENSION_TYPE_DATA_ELEMENT_OPERAND, + VIS_TYPE_OUTLIER_TABLE, VIS_TYPE_SINGLE_VALUE, VIS_TYPE_PIE, dimensionCreate, @@ -138,6 +141,54 @@ export const getItemsByDimensionFromUi = (ui) => { return result } +export const getOutlierTableCurrentFromUi = (state, value, metadata) => { + const ui = { + ...value, + layout: { + ...getAdaptedUiLayoutByType(value.layout, VIS_TYPE_OUTLIER_TABLE), + }, + itemsByDimension: getItemsByDimensionFromUi(value), + } + + const axesFromUi = getAxesFromUi(ui) + + const peItems = layoutGetDimensionItems(axesFromUi, DIMENSION_ID_PERIOD) + const dxItems = layoutGetDimensionItems(axesFromUi, DIMENSION_ID_DATA) + + const outlierTableAxesFromUi = + // only save the first pe item + layoutReplaceDimension( + // only save data element and data element operand dx items + layoutReplaceDimension( + axesFromUi, + DIMENSION_ID_DATA, + dxItems.filter(({ id }) => + [ + DIMENSION_TYPE_DATA_ELEMENT, + DIMENSION_TYPE_DATA_ELEMENT_OPERAND, + ].includes(metadata[id]?.dimensionItemType) + ) + ), + DIMENSION_ID_PERIOD, + [peItems[0]] + ) + + return { + ...state, + [BASE_FIELD_TYPE]: ui.type, + ...outlierTableAxesFromUi, + ...getOptionsFromUi(ui), + sorting: ui.sorting + ? [ + { + dimension: ui.sorting.dimension, + direction: ui.sorting.direction.toUpperCase(), + }, + ] + : undefined, + } +} + export const getSingleValueCurrentFromUi = (state, value) => { const ui = { ...value, diff --git a/src/modules/error.js b/src/modules/error.js index 07160d1aa9..06110ae409 100644 --- a/src/modules/error.js +++ b/src/modules/error.js @@ -357,6 +357,18 @@ export class AnalyticsRequestError extends VisualizationError { } } +export class NoOutliersError extends VisualizationError { + constructor() { + super( + EmptyBox, + i18n.t('No outliers found'), + i18n.t( + 'There were no outliers found for the selected data items and options.' + ) + ) + } +} + export const genericErrorTitle = i18n.t('Something went wrong') const getAvailableAxesDescription = (visType) => { diff --git a/src/modules/fetchData.js b/src/modules/fetchData.js index d935136fc0..0126e720d0 100644 --- a/src/modules/fetchData.js +++ b/src/modules/fetchData.js @@ -1,11 +1,13 @@ import { isYearOverYear, + isOutlierTable, DIMENSION_ID_PERIOD, layoutGetDimensionItems, ALL_DYNAMIC_DIMENSION_ITEMS, } from '@dhis2/analytics' import { apiFetchAnalyticsForYearOverYear, + apiFetchAnalyticsForOutlierTable, apiFetchAnalytics, } from '../api/analytics.js' import { @@ -94,6 +96,17 @@ export const fetchData = async ({ } } + if (isOutlierTable(adaptedVisualization.type)) { + return { + responses: await apiFetchAnalyticsForOutlierTable( + dataEngine, + adaptedVisualization, + options + ), + extraOptions, + } + } + return { responses: await apiFetchAnalytics( dataEngine, diff --git a/src/modules/fields/baseFields.js b/src/modules/fields/baseFields.js index 5d856105c3..462ae2420a 100644 --- a/src/modules/fields/baseFields.js +++ b/src/modules/fields/baseFields.js @@ -117,6 +117,7 @@ export const fieldsByType = { getFieldObject('relativePeriods', { excluded: true }), getFieldObject('rows'), getFieldObject('shortName'), + getFieldObject('sorting'), getFieldObject('sortOrder', { option: true }), getFieldObject('subscribed'), getFieldObject('subscribers'), diff --git a/src/modules/layoutValidation.js b/src/modules/layoutValidation.js index c2d9776abb..9a7fdf3c2e 100644 --- a/src/modules/layoutValidation.js +++ b/src/modules/layoutValidation.js @@ -8,6 +8,7 @@ import { VIS_TYPE_SINGLE_VALUE, VIS_TYPE_PIVOT_TABLE, VIS_TYPE_SCATTER, + VIS_TYPE_OUTLIER_TABLE, getPredefinedDimensionProp, dimensionIsValid, layoutGetDimension, @@ -128,6 +129,18 @@ const validateScatterLayout = (layout) => { ) } +const validateOutlierTableLayout = (layout) => { + validateDimension( + layoutGetDimension(layout, DIMENSION_ID_DATA), + new NoDataError(layout.type) + ) + + validateDimension( + layoutGetDimension(layout, DIMENSION_ID_PERIOD), + new NoPeriodError(layout.type) + ) +} + export const validateLayout = (layout) => { switch (layout.type) { case VIS_TYPE_PIE: @@ -142,6 +155,8 @@ export const validateLayout = (layout) => { return validatePivotTableLayout(layout) case VIS_TYPE_SCATTER: return validateScatterLayout(layout) + case VIS_TYPE_OUTLIER_TABLE: + return validateOutlierTableLayout(layout) default: return validateDefaultLayout(layout) } diff --git a/src/modules/options.js b/src/modules/options.js index 17bb319ca2..a6966b0f5a 100644 --- a/src/modules/options.js +++ b/src/modules/options.js @@ -13,6 +13,8 @@ export const OPTION_BASE_LINE_ENABLED = 'baseLineEnabled' export const OPTION_BASE_LINE_TITLE = 'baseLineTitle' export const OPTION_BASE_LINE_VALUE = 'baseLineValue' export const OPTION_BASE_LINE_TITLE_FONT_STYLE = 'baseLineTitleFontStyle' +export const OPTION_DISPLAY_DENSITY = 'displayDensity' +export const OPTION_FONT_SIZE = 'fontSize' export const OPTION_TARGET_LINE_ENABLED = 'targetLineEnabled' export const OPTION_TARGET_LINE_TITLE = 'targetLineTitle' export const OPTION_TARGET_LINE_VALUE = 'targetLineValue' @@ -21,6 +23,13 @@ export const OPTION_LEGEND_DISPLAY_STRATEGY = 'legendDisplayStrategy' export const OPTION_LEGEND_DISPLAY_STYLE = 'legendDisplayStyle' export const OPTION_LEGEND_SET = 'legendSet' +export const DISPLAY_DENSITY_COMFORTABLE = 'COMFORTABLE' +export const DISPLAY_DENSITY_NORMAL = 'NORMAL' +export const DISPLAY_DENSITY_COMPACT = 'COMPACT' +export const FONT_SIZE_LARGE = 'LARGE' +export const FONT_SIZE_NORMAL = 'NORMAL' +export const FONT_SIZE_SMALL = 'SMALL' + export const options = { axes: { requestable: false, savable: true, defaultValue: [] }, colorSet: { @@ -102,11 +111,15 @@ export const options = { numberType: { defaultValue: 'VALUE', requestable: false, savable: true }, showHierarchy: { defaultValue: false, requestable: true, savable: true }, displayDensity: { - defaultValue: 'NORMAL', + defaultValue: DISPLAY_DENSITY_NORMAL, + requestable: false, + savable: true, + }, + fontSize: { + defaultValue: FONT_SIZE_NORMAL, requestable: false, savable: true, }, - fontSize: { defaultValue: 'NORMAL', requestable: false, savable: true }, digitGroupSeparator: { defaultValue: 'SPACE', requestable: false, diff --git a/src/modules/options/config.js b/src/modules/options/config.js index b55b1c9d23..2d1f11b614 100644 --- a/src/modules/options/config.js +++ b/src/modules/options/config.js @@ -4,6 +4,7 @@ import { VIS_TYPE_GAUGE, VIS_TYPE_SINGLE_VALUE, VIS_TYPE_SCATTER, + VIS_TYPE_OUTLIER_TABLE, isStacked as isStackedType, isLegendSetType, isMultiType, @@ -13,6 +14,7 @@ import { } from '@dhis2/analytics' import defaultConfig from './defaultConfig.js' import gaugeConfig from './gaugeConfig.js' +import outlierTableConfig from './outlierTableConfig.js' import pieConfig from './pieConfig.js' import pivotTableConfig from './pivotTableConfig.js' import scatterConfig from './scatterConfig.js' @@ -60,6 +62,8 @@ export const getOptionsByType = ({ }) case VIS_TYPE_SCATTER: return scatterConfig() + case VIS_TYPE_OUTLIER_TABLE: + return outlierTableConfig(defaultProps) default: return defaultConfig(defaultProps) } diff --git a/src/modules/options/outlierTableConfig.js b/src/modules/options/outlierTableConfig.js new file mode 100644 index 0000000000..2e29b45619 --- /dev/null +++ b/src/modules/options/outlierTableConfig.js @@ -0,0 +1,40 @@ +import React from 'react' +import DigitGroupSeparator from '../../components/VisualizationOptions/Options/DigitGroupSeparator.js' +import DisplayDensity from '../../components/VisualizationOptions/Options/DisplayDensity.js' +import FontSize from '../../components/VisualizationOptions/Options/FontSize.js' +import Outliers from '../../components/VisualizationOptions/Options/OutliersForOutlierTable.js' +import OutliersMaxResults from '../../components/VisualizationOptions/Options/OutliersMaxResults.js' +import ShowHierarchy from '../../components/VisualizationOptions/Options/ShowHierarchy.js' +import SkipRounding from '../../components/VisualizationOptions/Options/SkipRounding.js' +import getDisplayTemplate from './sections/templates/display.js' +import getDataTab from './tabs/data.js' +import getOutliersTab from './tabs/outliers.js' +import getStyleTab from './tabs/style.js' + +export default () => [ + getDataTab([ + getDisplayTemplate({ + content: React.Children.toArray([ + , + , + ]), + }), + ]), + getStyleTab([ + { + key: 'style-section-1', + content: React.Children.toArray([ + , + , + , + , + ]), + }, + ]), + getOutliersTab([ + { + key: 'outliers-section-1', + content: React.Children.toArray([]), + }, + ]), +] diff --git a/src/modules/ui.js b/src/modules/ui.js index 39b6cc6f10..374210c18b 100644 --- a/src/modules/ui.js +++ b/src/modules/ui.js @@ -6,6 +6,7 @@ import { layoutGetDimensionIdItemIdsObject, VIS_TYPE_YEAR_OVER_YEAR_LINE, VIS_TYPE_YEAR_OVER_YEAR_COLUMN, + VIS_TYPE_OUTLIER_TABLE, VIS_TYPE_PIVOT_TABLE, VIS_TYPE_SCATTER, defaultVisType, @@ -14,6 +15,14 @@ import { VIS_TYPE_GAUGE, VIS_TYPE_SINGLE_VALUE, } from '@dhis2/analytics' +import { + DEFAULT_STATE as OUTLIER_METHOD_THRESHOLD_DEFAULT_STATE, + METHOD_MODIFIED_Z_SCORE, + METHOD_STANDARD_Z_SCORE, + OUTLIER_ANALYSIS_OPTION_NAME, + OUTLIER_METHOD_PROP, +} from '../components/VisualizationOptions/Options/OutliersForOutlierTable.js' +import { DEFAULT_STATE as OUTLIER_MAX_RESULTS_DEFAULT_STATE } from '../components/VisualizationOptions/Options/OutliersMaxResults.js' import { getDisabledOptions } from './disabledOptions.js' import { BASE_FIELD_YEARLY_SERIES } from './fields/baseFields.js' import { getInverseLayout } from './layout.js' @@ -25,6 +34,11 @@ export const SERIES_ITEM_AXIS_PROP = 'axis' export const ITEM_ATTRIBUTE_VERTICAL = 'VERTICAL' export const ITEM_ATTRIBUTE_HORIZONTAL = 'HORIZONTAL' +export const getDefaultSorting = () => ({ + dimension: 'value', + direction: 'desc', +}) + // Transform from backend model to store.ui format export const getUiFromVisualization = (vis, currentState = {}) => { const visType = vis.type || defaultVisType @@ -106,6 +120,27 @@ const scatterUiAdapter = (ui) => { return adaptedUi } +// Transform from store.ui to outlier table format +const outlierTableUiAdapter = (ui) => { + const adaptedUi = defaultUiAdapter(ui) + + const outlierAnalysis = ui.options?.[OUTLIER_ANALYSIS_OPTION_NAME] + + if ( + !outlierAnalysis || + ![METHOD_STANDARD_Z_SCORE, METHOD_MODIFIED_Z_SCORE].includes( + outlierAnalysis[OUTLIER_METHOD_PROP] + ) + ) { + adaptedUi.options[OUTLIER_ANALYSIS_OPTION_NAME] = { + ...OUTLIER_METHOD_THRESHOLD_DEFAULT_STATE, + ...OUTLIER_MAX_RESULTS_DEFAULT_STATE, + } + } + + return adaptedUi +} + export const getAdaptedUiByType = (ui) => { let adaptedUi @@ -125,6 +160,9 @@ export const getAdaptedUiByType = (ui) => { case VIS_TYPE_SCATTER: adaptedUi = scatterUiAdapter(ui) break + case VIS_TYPE_OUTLIER_TABLE: + adaptedUi = outlierTableUiAdapter(ui) + break default: adaptedUi = defaultUiAdapter(ui) break @@ -178,3 +216,12 @@ export const mergeUiMaps = (destinationMap, sourceMap, propName) => { destinationMap[key][propName] = sourceMap[key] }) } + +export const getSortingFromVisualization = (visualization) => { + return visualization.sorting?.length + ? { + dimension: visualization.sorting[0].dimension, + direction: visualization.sorting[0].direction.toLowerCase(), + } + : undefined +} diff --git a/src/modules/visualization.js b/src/modules/visualization.js index a762cbc9b1..b58247bec2 100644 --- a/src/modules/visualization.js +++ b/src/modules/visualization.js @@ -14,7 +14,9 @@ import { VIS_TYPE_YEAR_OVER_YEAR_COLUMN, VIS_TYPE_SINGLE_VALUE, VIS_TYPE_SCATTER, + VIS_TYPE_OUTLIER_TABLE, } from '@dhis2/analytics' +import { useConfig } from '@dhis2/app-runtime' import i18n from '@dhis2/d2-i18n' import { DEFAULT_CURRENT } from '../reducers/current.js' import { DEFAULT_VISUALIZATION } from '../reducers/visualization.js' @@ -36,6 +38,7 @@ export const visTypes = [ VIS_TYPE_YEAR_OVER_YEAR_COLUMN, VIS_TYPE_SINGLE_VALUE, VIS_TYPE_SCATTER, + VIS_TYPE_OUTLIER_TABLE, ] export const getVisTypeDescriptions = () => ({ @@ -82,7 +85,10 @@ export const getVisTypeDescriptions = () => ({ 'Display a single value. Recommend relative period to show latest data.' ), [VIS_TYPE_SCATTER]: i18n.t( - 'View the relationship between two data items at a place or time. Recommended for finding outliers.' + 'Compare the relationship between two data items across multiple places. Recommended for visualizing outliers.' + ), + [VIS_TYPE_OUTLIER_TABLE]: i18n.t( + 'Automatically identify extreme outliers based on historical data.' ), }) @@ -138,3 +144,14 @@ export const getDimensionMetadataFromVisualization = (visualization) => { return metadata } + +export const useVisTypesFilterByVersion = () => { + const { serverVersion } = useConfig() + + const filterVisTypesByVersion = (visType) => + serverVersion.minor < 41 && visType === VIS_TYPE_OUTLIER_TABLE + ? false + : true + + return filterVisTypesByVersion +} diff --git a/src/reducers/current.js b/src/reducers/current.js index d81eca19d0..082d524db6 100644 --- a/src/reducers/current.js +++ b/src/reducers/current.js @@ -3,6 +3,7 @@ import { VIS_TYPE_YEAR_OVER_YEAR_COLUMN, VIS_TYPE_PIE, VIS_TYPE_GAUGE, + VIS_TYPE_OUTLIER_TABLE, VIS_TYPE_SINGLE_VALUE, VIS_TYPE_SCATTER, } from '@dhis2/analytics' @@ -11,6 +12,7 @@ import { getOptionsFromUi, getPieCurrentFromUi, getYearOverYearCurrentFromUi, + getOutlierTableCurrentFromUi, getSingleValueCurrentFromUi, getSeriesFromUi, getScatterCurrentFromUi, @@ -60,6 +62,12 @@ export default (state = DEFAULT_CURRENT, action) => { return getYearOverYearCurrentFromUi(state, action.value.ui) case VIS_TYPE_SCATTER: return getScatterCurrentFromUi(state, action.value.ui) + case VIS_TYPE_OUTLIER_TABLE: + return getOutlierTableCurrentFromUi( + state, + action.value.ui, + action.value.metadata + ) default: { return getDefaultFromUi( state, diff --git a/src/reducers/ui.js b/src/reducers/ui.js index d6c7113e53..11f49c2fa8 100644 --- a/src/reducers/ui.js +++ b/src/reducers/ui.js @@ -16,6 +16,7 @@ import { FONT_STYLE_VERTICAL_AXIS_TITLE, FONT_STYLE_REGRESSION_LINE_LABEL, USER_ORG_UNIT, + VIS_TYPE_OUTLIER_TABLE, } from '@dhis2/analytics' import objectClean from 'd2-utilizr/lib/objectClean' import castArray from 'lodash-es/castArray' @@ -23,6 +24,8 @@ import { TITLE_AUTO, TITLE_CUSTOM, } from '../components/VisualizationOptions/Options/AxisTitle.js' +import { DEFAULT_STATE as OUTLIER_METHOD_THRESHOLD_DEFAULT_STATE } from '../components/VisualizationOptions/Options/OutliersForOutlierTable.js' +import { DEFAULT_STATE as OUTLIER_MAX_RESULTS_DEFAULT_STATE } from '../components/VisualizationOptions/Options/OutliersMaxResults.js' import { getFilteredLayout, getInverseLayout, @@ -52,6 +55,7 @@ import { } from '../modules/options.js' import { getAdaptedUiByType, + getDefaultSorting, getUiFromVisualization, SERIES_ITEM_TYPE_PROP, } from '../modules/ui.js' @@ -63,6 +67,8 @@ export const SET_UI_DISABLED_OPTIONS = 'SET_UI_DISABLED_OPTIONS' export const SET_UI_OPTIONS = 'SET_UI_OPTIONS' export const SET_UI_OPTION = 'SET_UI_OPTION' export const SET_UI_OPTION_FONT_STYLE = 'SET_UI_OPTION_FONT_STYLE' +export const SET_UI_DATA_SORTING = 'SET_UI_DATA_SORTING' +export const CLEAR_UI_DATA_SORTING = 'CLEAR_UI_DATA_SORTING' export const SET_UI_LAYOUT = 'SET_UI_LAYOUT' export const ADD_UI_LAYOUT_DIMENSIONS = 'ADD_UI_LAYOUT_DIMENSIONS' export const REMOVE_UI_LAYOUT_DIMENSIONS = 'REMOVE_UI_LAYOUT_DIMENSIONS' @@ -99,7 +105,6 @@ export const DEFAULT_UI = { parentGraphMap: {}, activeModalDialog: null, rightSidebarOpen: false, - outlierAnalysis: null, } export const PRESELECTED_YEAR_OVER_YEAR_SERIES = ['THIS_YEAR', 'LAST_YEAR'] @@ -134,12 +139,35 @@ const getPreselectedUi = (options) => { } } +const getDefaultUiByType = (ui) => { + switch (ui.type) { + case VIS_TYPE_OUTLIER_TABLE: { + return { + ...ui, + options: { + ...ui.options, + outlierAnalysis: { + ...(ui.options.outlierAnalysis ?? { + ...OUTLIER_METHOD_THRESHOLD_DEFAULT_STATE, + ...OUTLIER_MAX_RESULTS_DEFAULT_STATE, + }), + }, + }, + sorting: { + ...(ui.sorting ?? getDefaultSorting()), + }, + } + } + default: { + return { ...ui } + } + } +} + export default (state = DEFAULT_UI, action) => { switch (action.type) { case SET_UI: { - return { - ...action.value, - } + return getDefaultUiByType(action.value) } case SET_UI_FROM_VISUALIZATION: { return getAdaptedUiByType( @@ -454,6 +482,20 @@ export default (state = DEFAULT_UI, action) => { }, } } + case CLEAR_UI_DATA_SORTING: { + return { + ...state, + sorting: undefined, + } + } + case SET_UI_DATA_SORTING: { + return { + ...state, + sorting: { + ...action.value, + }, + } + } case SET_UI_LAYOUT: { return { ...state, diff --git a/yarn.lock b/yarn.lock index bdccf01e0b..c5a6901acf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1448,590 +1448,590 @@ debug "^3.1.0" lodash.once "^4.1.1" -"@dhis2-ui/alert@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/alert/-/alert-8.12.4.tgz#bb0b39a7d364661c5c6b4cfdd1611d9732ba18d9" - integrity sha512-YM5tUF728fYZUJvWJP1gKvdHxuXRPMCrIs17tBGO6IYfuproMwUklmnSXE8oIN4yOXqd4b4RkKLNOJsPDbcvfQ== +"@dhis2-ui/alert@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/alert/-/alert-9.2.0.tgz#3b30e90a88a96c617219c60ad8bc16a5969e85d1" + integrity sha512-9uh6vIKlj9vm9wOr/7UvSvxrEgl0p99+FdfFtvCeiOywR2+CCPvd9fRsL/vWKcCraopOriCmTm5PzBUWPuatvg== dependencies: - "@dhis2-ui/portal" "8.12.4" + "@dhis2-ui/portal" "9.2.0" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" - "@dhis2/ui-icons" "8.12.4" + "@dhis2/ui-constants" "9.2.0" + "@dhis2/ui-icons" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/box@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/box/-/box-8.12.4.tgz#f28202880bb27d277cc7c73714325dafaf38e699" - integrity sha512-OqIcTQCoUHXM9vjAs4X3oQD+oravJoDs2HkeFu5w3XH5E+TrqkUVhcHjHNN8LNLQgC1YQv7qyU/Q21Cra21zMQ== +"@dhis2-ui/box@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/box/-/box-9.2.0.tgz#0f8fd79059d724c075180ea442c860ebff88c802" + integrity sha512-ERNEd8lDAQIGDmTYknWClPGbmWuOpFAnE8XurB6wrkydn0M2+wsIa00q2on6RgY2YaAjVtYZEXr9CfjBtwh04w== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" + "@dhis2/ui-constants" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/button@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/button/-/button-8.12.4.tgz#61b574c24741edaf31027a6563f7b030759a4bb1" - integrity sha512-0zpjbbR5neFpsTD/pFStsjjSqBccM0nNAaN8n4fbJAjgmjlzrCf8ERFd1KA6YuWZgKKt/F3sF5qoAKAm+UJ4uw== +"@dhis2-ui/button@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/button/-/button-9.2.0.tgz#8d465f471ca629e1cb0ded01fc68143eb4960c1c" + integrity sha512-YBdIpaOqEgpI0JhHm8XBlpFGDF7O6eiFQ4Noxu131KEyDOf7a+AkIyECVZw6Jsj3n2zrbyzBDw6IndSFSlBqRQ== dependencies: - "@dhis2-ui/layer" "8.12.4" - "@dhis2-ui/loader" "8.12.4" - "@dhis2-ui/popper" "8.12.4" + "@dhis2-ui/layer" "9.2.0" + "@dhis2-ui/loader" "9.2.0" + "@dhis2-ui/popper" "9.2.0" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" - "@dhis2/ui-icons" "8.12.4" + "@dhis2/ui-constants" "9.2.0" + "@dhis2/ui-icons" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/calendar@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/calendar/-/calendar-8.12.4.tgz#b10a3066d4f5638939a7dc0ec3a431e37066c1fb" - integrity sha512-F8e6YUqh6RMFehJxI4XiTXkbnHhrOzdRismQageAr1nQmloQPYQEZabI/KVsCec8r0nH9Lrr3D/qNbzLx4irMg== +"@dhis2-ui/calendar@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/calendar/-/calendar-9.2.0.tgz#c3fbcdc622a13dd112de163719cfdcd0f960cfe2" + integrity sha512-6xxkCx66tjfGffGoPnDB6pe9Pvx+UybAGCqof7/0xfHP2vnfdss716imM02HfJRZwp5dNUrXqBO9uuQ1qkp/HA== dependencies: - "@dhis2-ui/button" "8.12.4" - "@dhis2-ui/card" "8.12.4" - "@dhis2-ui/input" "8.12.4" - "@dhis2-ui/layer" "8.12.4" - "@dhis2-ui/popper" "8.12.4" + "@dhis2-ui/button" "9.2.0" + "@dhis2-ui/card" "9.2.0" + "@dhis2-ui/input" "9.2.0" + "@dhis2-ui/layer" "9.2.0" + "@dhis2-ui/popper" "9.2.0" "@dhis2/multi-calendar-dates" "1.0.2" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" - "@dhis2/ui-icons" "8.12.4" + "@dhis2/ui-constants" "9.2.0" + "@dhis2/ui-icons" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/card@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/card/-/card-8.12.4.tgz#e73b936c352dff26ce432adb09c12163ee20cc88" - integrity sha512-PTMVMTvfOAjP2/0AfDHk7iKFlJQkSv/7aE/X+zZz4l8ltWvxuQ6eLH6Az2BneVkkQBO6eYri1q9dVzf1CHpnjA== +"@dhis2-ui/card@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/card/-/card-9.2.0.tgz#1a2d8f2f9becfd9fd43e7e315fda511418698fd1" + integrity sha512-++eLieBc4WsNXD1iQEQaqijnQ7hpKu0HH9cjkwTa46jjI9SoJGdYInvCzZPafB1XuCZ42f08kTcdND2qZWbssQ== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" + "@dhis2/ui-constants" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/center@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/center/-/center-8.12.4.tgz#45b2a14b0d37d44026e2a6214713c7abbc740cd3" - integrity sha512-TAX9NnqojEx6Jex5HCGNOACOZ3Cb1EOEFbY++2OQqZDWDri4SMCz1EmTxo81XlJMwesXFhjlBOw5bRfxPlArsA== +"@dhis2-ui/center@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/center/-/center-9.2.0.tgz#f7fbcbd00696d718286f269a64a5bad327160449" + integrity sha512-IBwLld/SQEtT5acjBr3nJnpkXNzypxm9f+QfdGYRq2ZSQmkIv9IUjIvlC25CU72Ksq+N0nWvA8AYp2Qx/l04fg== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" + "@dhis2/ui-constants" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/checkbox@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/checkbox/-/checkbox-8.12.4.tgz#9aa61bad20cde8de09918dc7a1d972a933fd0554" - integrity sha512-2oacza/ZkUDECPlInbNcyn3jfjSfbyVm0uKpzB/lMBnjdfK3UCsD4PvDPydjaUR6hox8Ae5vtBcRprwyOxx+Kg== +"@dhis2-ui/checkbox@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/checkbox/-/checkbox-9.2.0.tgz#7f27c645bb9d31df4898c9c96cfdabf9ddd01ab3" + integrity sha512-EIGbkdnCLOyW3s7Mh31LdPgghHPaTAt0MJe5ieF/1NycUCfx99NGoR7wXlAlI6wNvjkuTYnYBknERGHUEzY9rA== dependencies: - "@dhis2-ui/field" "8.12.4" - "@dhis2-ui/required" "8.12.4" + "@dhis2-ui/field" "9.2.0" + "@dhis2-ui/required" "9.2.0" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" + "@dhis2/ui-constants" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/chip@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/chip/-/chip-8.12.4.tgz#24bcb4bc9dcfc146a7dd535d5fe04dff1ea5c528" - integrity sha512-ryN9gOvD9IJHSqgf3xV4TlVRuEGoJ24yVNKa0PVXAEoYAzr/T3Mw7n5R8dCJs+Ykes1u1IcPPq9OTibSZF+3YA== +"@dhis2-ui/chip@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/chip/-/chip-9.2.0.tgz#45b5e55e6d11318bb6917f1bcdee9bf9b67bd620" + integrity sha512-PesbKz0MrXegDAFcGHq34Ast0kM/mJrmExiLua262egYYaRiQXDYeaNuS2TT+qPsPsDva2Pp7kqjdrFRDL2Few== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" + "@dhis2/ui-constants" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/cover@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/cover/-/cover-8.12.4.tgz#39534bd9e4a79d18f7701c2dbcc315abd505251f" - integrity sha512-tONUDufYfK4RhnWGPLp5tH5CrVgF3/MNyrGp/emsCtLnLz0hetUoBZctZPjKyL7IFH69PxohXooOCbTZ5jl8AA== +"@dhis2-ui/cover@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/cover/-/cover-9.2.0.tgz#0288dad0ba9caf8c0b77c86868fc4bae3388f6c7" + integrity sha512-ZxcOOuyy/dB4pgjdVzBlgwtVue0HEct2XOiuIKM66F5G0DPBqz6UtLDD+8VRofQwTKcTYQh9kPZPmx/9q65Zcg== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" + "@dhis2/ui-constants" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/css@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/css/-/css-8.12.4.tgz#6113ac65629a6cfc079b1fe07cd03d19b27a0029" - integrity sha512-vMeQpeMziHAo92zWzNtpXjegXtFZFczZwTknc+xj2AkvBk0uJkbje7iyDXdAOO+okuVBhFtuFXSz7IqXC+4TgA== +"@dhis2-ui/css@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/css/-/css-9.2.0.tgz#ad881b90371c7506690ee2a2b2151c09f793e368" + integrity sha512-dBe9S75+Nj1c9SNRkxU0VWTVwFZJ+vLFKxD1UYaPdbJ2DHD6AD4UJ1YtXgvBvgiJ0wbT1vesh3tKfEUXbnTmkw== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" + "@dhis2/ui-constants" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/divider@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/divider/-/divider-8.12.4.tgz#8b757573e2af5ff6d74f3beaf6e8f06bc2494874" - integrity sha512-1zp0H1su9M2UVzDNHJWxl+uj3zy2/T8QxZMhNO99y+7QXKZFSFT/7x4ZDxBTGcePVMA1RyQ3FhmI0TaJHXv7HQ== +"@dhis2-ui/divider@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/divider/-/divider-9.2.0.tgz#2f9ea2f3a1f89853963bcffdeef27e5ebc390021" + integrity sha512-z87v4XKIO1IXhWmHYhCQgR7MTiuU+zLMg9Py2OIDxMchVXrdyGSeKCpL9UgOzl/jtHxwoMdnVDzy8sLLzITgsw== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" + "@dhis2/ui-constants" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/field@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/field/-/field-8.12.4.tgz#f0e3251cad00acb32ea808d12812a1959639a4ea" - integrity sha512-wHkBPc4FaGAvGLnNMktzKs8aHaVByAi0BBjzvG9iqMAz4t7LKjltJ+CBFqGZyrBBc7/JExUOPOB6+6YGn3r8Lw== +"@dhis2-ui/field@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/field/-/field-9.2.0.tgz#d16b443ff168e352b6f5168d2179975377902dd1" + integrity sha512-pZr1LkzOm4TOPdPAen9e3RHQE8Y1uCbdFhP1oc5vg44etmudX7stJuEXP6/DrPm6sHCJRojf90laqPai1vfrHg== dependencies: - "@dhis2-ui/box" "8.12.4" - "@dhis2-ui/help" "8.12.4" - "@dhis2-ui/label" "8.12.4" + "@dhis2-ui/box" "9.2.0" + "@dhis2-ui/help" "9.2.0" + "@dhis2-ui/label" "9.2.0" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" + "@dhis2/ui-constants" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/file-input@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/file-input/-/file-input-8.12.4.tgz#e5cd0e0081d9fd75a316b57f2d1d830466d1cca4" - integrity sha512-zA71nMufBq6Ch1zMy+xiHooJV+ETBYpSJRLDTCvgFZ+G8NzXke7Pqryc8HPJFEQChWI9w+ihsNUNwaoGWZs16Q== +"@dhis2-ui/file-input@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/file-input/-/file-input-9.2.0.tgz#d48e9d290aea798640b251d915b9a8f51abcf363" + integrity sha512-x+lKilo6aQN320fIAPZMAq13PxKuTb/jhZb7m7pMS0HKsPqfT5rWm1VfmBlN/02BkKsmhsvo/tTe9vxpAic71A== dependencies: - "@dhis2-ui/button" "8.12.4" - "@dhis2-ui/field" "8.12.4" - "@dhis2-ui/label" "8.12.4" - "@dhis2-ui/loader" "8.12.4" - "@dhis2-ui/status-icon" "8.12.4" + "@dhis2-ui/button" "9.2.0" + "@dhis2-ui/field" "9.2.0" + "@dhis2-ui/label" "9.2.0" + "@dhis2-ui/loader" "9.2.0" + "@dhis2-ui/status-icon" "9.2.0" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" - "@dhis2/ui-icons" "8.12.4" + "@dhis2/ui-constants" "9.2.0" + "@dhis2/ui-icons" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/header-bar@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/header-bar/-/header-bar-8.12.4.tgz#80ff0fa89957cb6ed2a623f0d95464a3453c0451" - integrity sha512-0JdbpL4eNVzBO+n4b0ozkvyKvlQE5TVGL9P/T2Gf+FbhQSkXc2NIbvIguozgEdbOXkEnScRCLAKx9K7Ot2iSuQ== - dependencies: - "@dhis2-ui/box" "8.12.4" - "@dhis2-ui/button" "8.12.4" - "@dhis2-ui/card" "8.12.4" - "@dhis2-ui/center" "8.12.4" - "@dhis2-ui/divider" "8.12.4" - "@dhis2-ui/input" "8.12.4" - "@dhis2-ui/layer" "8.12.4" - "@dhis2-ui/loader" "8.12.4" - "@dhis2-ui/logo" "8.12.4" - "@dhis2-ui/menu" "8.12.4" - "@dhis2-ui/modal" "8.12.4" - "@dhis2-ui/user-avatar" "8.12.4" +"@dhis2-ui/header-bar@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/header-bar/-/header-bar-9.2.0.tgz#83efe93821318decca3bd1a28d30fd3089435b6d" + integrity sha512-7iNKOllQNeYfa9nJjXVqiohcuKEk+RMhuu/7BY8g2oPmwbvS0uc49KiL53cFfAWBST/v5fU1XOpWS/N7PmJr2g== + dependencies: + "@dhis2-ui/box" "9.2.0" + "@dhis2-ui/button" "9.2.0" + "@dhis2-ui/card" "9.2.0" + "@dhis2-ui/center" "9.2.0" + "@dhis2-ui/divider" "9.2.0" + "@dhis2-ui/input" "9.2.0" + "@dhis2-ui/layer" "9.2.0" + "@dhis2-ui/loader" "9.2.0" + "@dhis2-ui/logo" "9.2.0" + "@dhis2-ui/menu" "9.2.0" + "@dhis2-ui/modal" "9.2.0" + "@dhis2-ui/user-avatar" "9.2.0" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" - "@dhis2/ui-icons" "8.12.4" + "@dhis2/ui-constants" "9.2.0" + "@dhis2/ui-icons" "9.2.0" classnames "^2.3.1" moment "^2.29.1" prop-types "^15.7.2" -"@dhis2-ui/help@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/help/-/help-8.12.4.tgz#e5d1c4193abf43ad0ac4182516992f10ab83a564" - integrity sha512-fZYZzu5waXz7yuijQiXYrGRYJk6xCvy8HdIKznqjiW4mpKxpd6Gh8nzpYgxz1j9WHgIjIZqj4/sNkMQ2eC4zOw== +"@dhis2-ui/help@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/help/-/help-9.2.0.tgz#76556c0d581fb0f7611fe5c5bb0d9adf35ad0c8e" + integrity sha512-qD3oNEwEb+pT7jsD4ciHtu16KrxMySPWoqco5nJwoGbcZFLw/caEfkBo2IroImD0MxtI0mKt5emD7V2yXRWm7A== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" + "@dhis2/ui-constants" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/input@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/input/-/input-8.12.4.tgz#57645a19bf2060c60a2f8280e292ab7ec71b9e02" - integrity sha512-eJu2UmbEOq0IWDf0kFRVOucP31nJtRhUQSXWp+1PkpmmpOXitItqjdxO+IgUs9WSiOBW3HDRtGl40V7pdHFnJQ== +"@dhis2-ui/input@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/input/-/input-9.2.0.tgz#bdf7c72e11b818ed86e1e6335cd373ae037f4ef3" + integrity sha512-0bzF/8pZSMqe5ZN2v0t0/rMTvKWd9kl5MDOy9fRXpX6yoFgfH+j+iIU06eVyqJl3DMqCdInfapJvpJR7MHvd+g== dependencies: - "@dhis2-ui/box" "8.12.4" - "@dhis2-ui/field" "8.12.4" - "@dhis2-ui/input" "8.12.4" - "@dhis2-ui/loader" "8.12.4" - "@dhis2-ui/status-icon" "8.12.4" + "@dhis2-ui/box" "9.2.0" + "@dhis2-ui/field" "9.2.0" + "@dhis2-ui/input" "9.2.0" + "@dhis2-ui/loader" "9.2.0" + "@dhis2-ui/status-icon" "9.2.0" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" - "@dhis2/ui-icons" "8.12.4" + "@dhis2/ui-constants" "9.2.0" + "@dhis2/ui-icons" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/intersection-detector@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/intersection-detector/-/intersection-detector-8.12.4.tgz#94ff379c170d29c43e6c2f4aa21f675f94fd5e5f" - integrity sha512-jW3N2jvbmQIuKmrAKJxAIqb/NmlwXwmNPrN+yXGWCLuuDh1rx8srziGfBxQDaHzjLSZT4AO8596b/Z5TxzTvrA== +"@dhis2-ui/intersection-detector@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/intersection-detector/-/intersection-detector-9.2.0.tgz#31f19bfb8645ebda7d7af6f6e641b40fa1e57888" + integrity sha512-erBoDMhOPmua8eP8bKJNl4WIUUm+Fw3Jj9M0OxC+xia3/Fi8scLUyk0Yek1Y0lGdb4YHJEXqx475dLNjpmiLRQ== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" + "@dhis2/ui-constants" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/label@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/label/-/label-8.12.4.tgz#3b1ec6759db4dee9ed8bfcbd3bd4f2c4a69a5c47" - integrity sha512-c+2PM/ijhuHAaFZTo+FxhN2gSZxgI6YsYpn6Nx9SZNBc7YR9gKvdxjSY1xsuD0Q6y1gRmhyjYK8ZatAYgLoJIw== +"@dhis2-ui/label@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/label/-/label-9.2.0.tgz#81ec2de2ea1b9bc7cf25eb8bb036d451d5b61748" + integrity sha512-k9Q4XyIbaNRvCn1+rLcEb/iDi479S3fOEJ2MCT6wsGxr1+Hys7yVw7Ggq0OQ9SLCEwElNQcvj+vWB4dZltXl5Q== dependencies: - "@dhis2-ui/required" "8.12.4" + "@dhis2-ui/required" "9.2.0" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" + "@dhis2/ui-constants" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/layer@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/layer/-/layer-8.12.4.tgz#8db65c8d5e9f5a35e1e5b897fe7a4cc67d05272b" - integrity sha512-pYnlgVcsGLmJo13H/mfG5SGoNPRtyfVKZY0nUExT1iDJ3A5nT2g8qfw+O38zAK+JBTCGC1yk6M6h4xXwkDOXnw== +"@dhis2-ui/layer@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/layer/-/layer-9.2.0.tgz#7afcb2ef8571eb9c839fc522eb76ead70e395dbc" + integrity sha512-95aFgQYxgJ7GmWY6AOSoAH4BH7wkIyUAioAIRUDwp0mmSJhJxG6P9b1PFqw4koX1zV4/RLoz+NiQ7Twv+03CLQ== dependencies: - "@dhis2-ui/portal" "8.12.4" + "@dhis2-ui/portal" "9.2.0" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" + "@dhis2/ui-constants" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/legend@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/legend/-/legend-8.12.4.tgz#cd831e98399ffffa6430c93da2831b432f75f0d5" - integrity sha512-Lmss3ITK2xyVHYvPFuWjbWto36+QqETqQR1/k4wPy5UkyedkTVbpX4SHyRv/x2m3To+X/NKB/JNomUn7tqHVYQ== +"@dhis2-ui/legend@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/legend/-/legend-9.2.0.tgz#a051822fd67c1442b6986a86097aca94f3960dd2" + integrity sha512-IfJhu48eu//O9AlHt7IUdsv5E92XG7v/95laFfyQOaGhf1C4BQf11s6yXc3nTFoil/p55oAsZnWp5e7UXcgrhg== dependencies: - "@dhis2-ui/required" "8.12.4" + "@dhis2-ui/required" "9.2.0" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" + "@dhis2/ui-constants" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/loader@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/loader/-/loader-8.12.4.tgz#e24219c803c38fef57f8808d2c3d281be63edd1d" - integrity sha512-c5ipPqzd6oPyRfTwE7HGGuDCud6Vsw7dkhDE/WnSDOVhxEYaqDqDU6PoNjRZY1el9uG9RV/dPtpPx4qbxX5mVw== +"@dhis2-ui/loader@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/loader/-/loader-9.2.0.tgz#f45fdb19a37182351932cd3e11980d43b4895d40" + integrity sha512-M8tuLE5kgm7oHmIN6par8GfRDpmt+DXFU3cCSZdiYIUM6SQSD8G5LmA8AaIHR5h7l430d93vcw7RQL3eeE/svw== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" + "@dhis2/ui-constants" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/logo@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/logo/-/logo-8.12.4.tgz#269c4344ab4dd10bda324548c4e731372912d6d9" - integrity sha512-8dH5uaKxB0IFtNXa3T7/NsjsXWKg3nQMQ0V6iUtuPOyJDUgk0D04t0c3NggRPczVWNNc7B+9nEJuEcOVNgc56Q== +"@dhis2-ui/logo@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/logo/-/logo-9.2.0.tgz#0452aadd8e92afc1c558869a08c8e5d1f14ff55f" + integrity sha512-OEFrSpDijeCIhLjJ8vipDGRdihTgj8+iLcPLDrOeRAurlgJZJZiicxBKSuY6uc6p1/9QPccqX2huJjM1uNqo2g== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" + "@dhis2/ui-constants" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/menu@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/menu/-/menu-8.12.4.tgz#6358ae12bde6d6897415fb2237a3155fc7e171c8" - integrity sha512-JWy3iiBvXSCrrOXZV92glzBIxCYKEqG1e8Zl2rXa8z58zxN2RCg1apb08p48acNFC7f/PpW/kdQW6rePXo/faQ== +"@dhis2-ui/menu@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/menu/-/menu-9.2.0.tgz#996729545b29def9366a099005a17c781bdca8f6" + integrity sha512-8k15qmoqBWKo1Afj+QXFnDAIoDyAqosvq3j8M/+xM+cSn0H+6e3Q7UOp0ByVQlumoY5DyrT9Z7NojULIjFUifQ== dependencies: - "@dhis2-ui/card" "8.12.4" - "@dhis2-ui/divider" "8.12.4" - "@dhis2-ui/layer" "8.12.4" - "@dhis2-ui/popper" "8.12.4" - "@dhis2-ui/portal" "8.12.4" + "@dhis2-ui/card" "9.2.0" + "@dhis2-ui/divider" "9.2.0" + "@dhis2-ui/layer" "9.2.0" + "@dhis2-ui/popper" "9.2.0" + "@dhis2-ui/portal" "9.2.0" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" - "@dhis2/ui-icons" "8.12.4" + "@dhis2/ui-constants" "9.2.0" + "@dhis2/ui-icons" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/modal@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/modal/-/modal-8.12.4.tgz#ad42dc3dc4ae799fe9aff7ca344555bb4b31843d" - integrity sha512-yQhc08AtxLzar7UHIHdgKpWKL/gN4P76QDuKJkWvaD7O2BRfIudQ4ZSWdmzRnK+Qkbiz1xQL+nTNe8YQqU2WYA== +"@dhis2-ui/modal@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/modal/-/modal-9.2.0.tgz#7815efe62be4f482b2e2224ffe92981068b1bae9" + integrity sha512-gp+gCTmtoiLVAhigo1i6msE598qIGnkW6To+dTkUecvxyvni+DZTAulTmL62UtTzzjPjYO0yOqNTWQztbpj1KQ== dependencies: - "@dhis2-ui/card" "8.12.4" - "@dhis2-ui/center" "8.12.4" - "@dhis2-ui/layer" "8.12.4" - "@dhis2-ui/portal" "8.12.4" + "@dhis2-ui/card" "9.2.0" + "@dhis2-ui/center" "9.2.0" + "@dhis2-ui/layer" "9.2.0" + "@dhis2-ui/portal" "9.2.0" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" - "@dhis2/ui-icons" "8.12.4" + "@dhis2/ui-constants" "9.2.0" + "@dhis2/ui-icons" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/node@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/node/-/node-8.12.4.tgz#941f883dccdc181f6f3e730647aaed62e01cb784" - integrity sha512-8DUB95g/mJyRkVPvWgJIr74Cm15ECdRL2voa0NilXW3eAErQdtdQiiGUcwdCRjeMIRyAcD4PO6WEbHYad+Tmqg== +"@dhis2-ui/node@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/node/-/node-9.2.0.tgz#3560b3e7394d275c1bce3e800b2654cddcd2adb2" + integrity sha512-xx7P/6V7vq3JLXUUATKGGUCORHqQL74fsGYUd9a0izyUyq4h3pEHL9ZT6Cel+A0d5ODYn/j/Q6fHICZzg55FBQ== dependencies: - "@dhis2-ui/loader" "8.12.4" + "@dhis2-ui/loader" "9.2.0" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" + "@dhis2/ui-constants" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/notice-box@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/notice-box/-/notice-box-8.12.4.tgz#edf83663f7d5410668915d3590b1d60971a8b74c" - integrity sha512-Q3QP0XRu2ry+QgA5QzS+lEFhU/FH8NWDonYhtGJRpT1fjcnOMfRE5UfOmRDWvGajSCWkic8PsvxzR+tbDbY3BA== +"@dhis2-ui/notice-box@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/notice-box/-/notice-box-9.2.0.tgz#8c1fb4a2a780fea0fde4b8f9eaf8f73957187c99" + integrity sha512-DY3WYXj1hsOsiBHGaNrOeZ8h7SPaXox6iMCTzL/jLvnfmTrH7wy6SHRLQYWg0BMrDflhMJu9qhn0jtzhEXZNMQ== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" - "@dhis2/ui-icons" "8.12.4" + "@dhis2/ui-constants" "9.2.0" + "@dhis2/ui-icons" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/organisation-unit-tree@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/organisation-unit-tree/-/organisation-unit-tree-8.12.4.tgz#8c694d2eeb9d76ddea0a3250c8cad33d325251ba" - integrity sha512-fDKSxVvq6udGh+58X+xwCz8jZH4lBdnH/kCPIe61Tlt5Ef8Zk5CRwt5tqiW1REcfRjCeidkXToaWmbfx5o3Bmw== +"@dhis2-ui/organisation-unit-tree@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/organisation-unit-tree/-/organisation-unit-tree-9.2.0.tgz#ce33d91361145d6574ddbef3866b56be06cee6cb" + integrity sha512-PHm908gNGPhq5D655BI4lrB+hMqfISKASjoFCWxG2f9FU64/pvQ+snZQQwQFMAJYMd6FKw4GOP1isKz0jTGNuA== dependencies: - "@dhis2-ui/checkbox" "8.12.4" - "@dhis2-ui/loader" "8.12.4" - "@dhis2-ui/node" "8.12.4" + "@dhis2-ui/checkbox" "9.2.0" + "@dhis2-ui/loader" "9.2.0" + "@dhis2-ui/node" "9.2.0" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" + "@dhis2/ui-constants" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/pagination@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/pagination/-/pagination-8.12.4.tgz#390c9d0e243be6f7e884191025dfbdd23d0c62bf" - integrity sha512-U7zMVOCwSgmmobj++uYsmQGwMEa93UMgGBwDbTiQZ3d9akn4bqH3U2ZbEaR/KolxhvIx1aQtHba4zHePGLr/bg== +"@dhis2-ui/pagination@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/pagination/-/pagination-9.2.0.tgz#f38d1ada2bc9f9795dae8f86c52d54f29889dd17" + integrity sha512-jXJNQ8JOPweeMFCsPXgAb8dAx8J/rNTnExL8WA6rfRDWujOojLp8Gu2MrH5jlHRpCBWIl+aJO1I/ZKHekQOeDg== dependencies: - "@dhis2-ui/button" "8.12.4" - "@dhis2-ui/select" "8.12.4" + "@dhis2-ui/button" "9.2.0" + "@dhis2-ui/select" "9.2.0" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" - "@dhis2/ui-icons" "8.12.4" + "@dhis2/ui-constants" "9.2.0" + "@dhis2/ui-icons" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/popover@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/popover/-/popover-8.12.4.tgz#faa9bd6f3ac2c4ddbc9d553a6aff3e05138ccc83" - integrity sha512-YCyG9WzeghPtaZvylsbazEuFFMKnKcIrmBygAeyk2/V8YVDp5HBM25t8LenuD1N8Scuaxp5VHp/lUY4KiqkApA== +"@dhis2-ui/popover@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/popover/-/popover-9.2.0.tgz#3eccd6abde1da72089aa5feda5669ce4d1aa6f7e" + integrity sha512-7g8AHPrzUuMuv2MXpX5HdwiyO+peSGoq7hg6rHN/VDasIUvGS7vbaV4Xbxfd32fNmpHceBV7gMga31hRNaKtgw== dependencies: - "@dhis2-ui/layer" "8.12.4" - "@dhis2-ui/popper" "8.12.4" + "@dhis2-ui/layer" "9.2.0" + "@dhis2-ui/popper" "9.2.0" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" + "@dhis2/ui-constants" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/popper@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/popper/-/popper-8.12.4.tgz#6a8e154be0b4e608fdefafafbf28f854235a7bca" - integrity sha512-V6zQrlVV0yIwV9KaRQKw8LEwP80ahxpLpvXEIETVE1tAmCtHd9u+KbYG/xGU4VbfoXt1KF+cmU1gTUlq/z0Nmg== +"@dhis2-ui/popper@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/popper/-/popper-9.2.0.tgz#6d17ca49d7f0496289e11315d414095d93d179b5" + integrity sha512-6xWNvUQaDu8VE4rCa+uYOheb/4BD/52Cs23w2yt4lLAVrym4kV+0cnpHtlEG1ZuuVrK/yHelMjrmYxn6yJE41Q== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" + "@dhis2/ui-constants" "9.2.0" "@popperjs/core" "^2.10.1" classnames "^2.3.1" prop-types "^15.7.2" react-popper "^2.2.5" resize-observer-polyfill "^1.5.1" -"@dhis2-ui/portal@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/portal/-/portal-8.12.4.tgz#6a854d015919cbd9fc6a8ab560191d03c275e94e" - integrity sha512-+hw6dBkn2csuDGAv1tRxV4rajyYQVg08sIn5jS7zeNWIER7YTrORVb1IbRChWrC3dvOe7SVMfQ8QwxgslemgQg== +"@dhis2-ui/portal@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/portal/-/portal-9.2.0.tgz#12435f3a8886de685fc9ee4540639810eabf0191" + integrity sha512-a5DqmTG+pn4y1aXZWr8wEGlK4xRqdvnOWJVqO/6aebEn4ZtcPJT6yyXouviAM2yoW7+fDj6TZhKrZo3vgMLHlA== dependencies: classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/radio@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/radio/-/radio-8.12.4.tgz#3adf0982c22ee59008f601531ef4b864eccb3d9a" - integrity sha512-ljH3d5XgB7oaL8EZVoWP0LMs2IcY4eVD7DhMtfUYwOtDSDqEDSioXNOh88JSZom1S8pD3mvqelxFdiMO62qLyg== +"@dhis2-ui/radio@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/radio/-/radio-9.2.0.tgz#9223ada90fe2d0eae375e85e0d99e6eb81d5269a" + integrity sha512-rjcz05spFlvRL8fnkO/7/ckznY4agQLl5P7UKSFL3Kz0KxvnocmounTX1BDsm6iQhKKt2HqJCYShpzPhTXavSA== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" + "@dhis2/ui-constants" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/required@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/required/-/required-8.12.4.tgz#3c153a850a5bb5f1cd6e3549fa2ea606a538cbd6" - integrity sha512-eUcSq8Lactw2g0fRA7mUFGQi6/fYEmiiGjPuEZAdupfkTZBPaLgwkyb7rXLyYmwVsQm8f00clam41tyO1nMwOQ== +"@dhis2-ui/required@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/required/-/required-9.2.0.tgz#8d76c914c3eb483958284c8ddf52ef7095cc2f42" + integrity sha512-cNoQct7gVbrAdzGDDFLnfBktmwmxAhD48oEZ6z4TE7IrPi2N+idqOiQCTEVKEWmuY7VGv9TV0I/1OLX6oiuSvA== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" + "@dhis2/ui-constants" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/segmented-control@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/segmented-control/-/segmented-control-8.12.4.tgz#1c1750c4f9e7f4b8e0473297eab0d46c35e4cbea" - integrity sha512-4Tqy128V567PrINT5/4Vuh1oe3A9TrkS+s3sncX/b7Mp7cqi+SWLwp1f8bUNg1oz2mDWsH1VosgSGLOiZw1xPw== +"@dhis2-ui/segmented-control@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/segmented-control/-/segmented-control-9.2.0.tgz#2b230eea17e35194072544ebbfc68e2c896fb951" + integrity sha512-x4eDqGu4JFfFPQk06mg8YdUCjYeYyXCLmZ0gbGj1Zx03gxpMwb4/nVyGoAxZg31/IVkhBOeAwXlvt+ckNXovgw== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" + "@dhis2/ui-constants" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/select@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/select/-/select-8.12.4.tgz#91c48ead135e7f2d5ddbaad63a1dc03d9aee0d1a" - integrity sha512-bKyJNAMfqYBjlqd+diSDcfQBoW4S6etopTaJE0fpwYn/Sm3xOLhUrQoo7woipmbKa5W8Hjwvc/uq9NNBZBPKiA== - dependencies: - "@dhis2-ui/box" "8.12.4" - "@dhis2-ui/button" "8.12.4" - "@dhis2-ui/card" "8.12.4" - "@dhis2-ui/checkbox" "8.12.4" - "@dhis2-ui/chip" "8.12.4" - "@dhis2-ui/field" "8.12.4" - "@dhis2-ui/input" "8.12.4" - "@dhis2-ui/layer" "8.12.4" - "@dhis2-ui/loader" "8.12.4" - "@dhis2-ui/popper" "8.12.4" - "@dhis2-ui/status-icon" "8.12.4" +"@dhis2-ui/select@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/select/-/select-9.2.0.tgz#e854ecf6fd2580f9fb904301d1985923c1ade9cf" + integrity sha512-Hv0mRbpJNHJUa2lVivrWBt67iJ+4xTb7KxZkThISkmpQpzbWmhmJWlDbe0L+PdqOsaB2A70/N9HH4dhQyVP8vA== + dependencies: + "@dhis2-ui/box" "9.2.0" + "@dhis2-ui/button" "9.2.0" + "@dhis2-ui/card" "9.2.0" + "@dhis2-ui/checkbox" "9.2.0" + "@dhis2-ui/chip" "9.2.0" + "@dhis2-ui/field" "9.2.0" + "@dhis2-ui/input" "9.2.0" + "@dhis2-ui/layer" "9.2.0" + "@dhis2-ui/loader" "9.2.0" + "@dhis2-ui/popper" "9.2.0" + "@dhis2-ui/status-icon" "9.2.0" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" - "@dhis2/ui-icons" "8.12.4" + "@dhis2/ui-constants" "9.2.0" + "@dhis2/ui-icons" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/selector-bar@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/selector-bar/-/selector-bar-8.12.4.tgz#d2bbca5751c1f410dc0ac8ee9b076f697eed63c4" - integrity sha512-5WuSepQ1NUX8V2h9CsseyJQ2GhyBmomyH0X7aYmtksQdImSSUzyjSBufkScA3YIYP83wQmmQEx6/32pPfoWtuA== - dependencies: - "@dhis2-ui/button" "8.12.4" - "@dhis2-ui/card" "8.12.4" - "@dhis2-ui/layer" "8.12.4" - "@dhis2-ui/popper" "8.12.4" - "@dhis2/ui-constants" "8.12.4" - "@dhis2/ui-icons" "8.12.4" +"@dhis2-ui/selector-bar@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/selector-bar/-/selector-bar-9.2.0.tgz#23790f850bee1142e7bdffd711a2dea27dc78112" + integrity sha512-HDb/XKuUdD82vDh5mgz+OWIwpKjyiZEqek74y8g2r/AsMIl5Fe2rOyncOUPpGctxYeVp9u3gAlkFbIHd/qpv/w== + dependencies: + "@dhis2-ui/button" "9.2.0" + "@dhis2-ui/card" "9.2.0" + "@dhis2-ui/layer" "9.2.0" + "@dhis2-ui/popper" "9.2.0" + "@dhis2/ui-constants" "9.2.0" + "@dhis2/ui-icons" "9.2.0" "@testing-library/react" "^12.1.2" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/sharing-dialog@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/sharing-dialog/-/sharing-dialog-8.12.4.tgz#9df65a459af2ef0f1e6bc551abc765771f4913d0" - integrity sha512-B9GyPvSgtNZ7mv6KcmahG/1vF/7qGH9Elis4keHQpd8rBKLBYtONdxoOPh9a8OkWYAmwI9jVejIWQYVXT0duKg== - dependencies: - "@dhis2-ui/box" "8.12.4" - "@dhis2-ui/button" "8.12.4" - "@dhis2-ui/card" "8.12.4" - "@dhis2-ui/divider" "8.12.4" - "@dhis2-ui/input" "8.12.4" - "@dhis2-ui/layer" "8.12.4" - "@dhis2-ui/menu" "8.12.4" - "@dhis2-ui/modal" "8.12.4" - "@dhis2-ui/notice-box" "8.12.4" - "@dhis2-ui/popper" "8.12.4" - "@dhis2-ui/select" "8.12.4" - "@dhis2-ui/tab" "8.12.4" - "@dhis2-ui/tooltip" "8.12.4" - "@dhis2-ui/user-avatar" "8.12.4" +"@dhis2-ui/sharing-dialog@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/sharing-dialog/-/sharing-dialog-9.2.0.tgz#8818e5119c2113b3a54add3bd56dfe55ad2fc52a" + integrity sha512-h7chsY8XM2kw06r52pGRjS8ZknuwiEkRxuBTXD77G1Loni5TMm6xr0OFr9nSQHSsbcbxydbo2WPRPAu6WjVyfg== + dependencies: + "@dhis2-ui/box" "9.2.0" + "@dhis2-ui/button" "9.2.0" + "@dhis2-ui/card" "9.2.0" + "@dhis2-ui/divider" "9.2.0" + "@dhis2-ui/input" "9.2.0" + "@dhis2-ui/layer" "9.2.0" + "@dhis2-ui/menu" "9.2.0" + "@dhis2-ui/modal" "9.2.0" + "@dhis2-ui/notice-box" "9.2.0" + "@dhis2-ui/popper" "9.2.0" + "@dhis2-ui/select" "9.2.0" + "@dhis2-ui/tab" "9.2.0" + "@dhis2-ui/tooltip" "9.2.0" + "@dhis2-ui/user-avatar" "9.2.0" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" - "@dhis2/ui-icons" "8.12.4" + "@dhis2/ui-constants" "9.2.0" + "@dhis2/ui-icons" "9.2.0" "@react-hook/size" "^2.1.2" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/status-icon@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/status-icon/-/status-icon-8.12.4.tgz#31eacb7ad5472384338ed3f5f5b405e6d3359635" - integrity sha512-q86WTwS2rGQNr7l0IP+ln0i2LVQm9UuDZKjZRmrtWMwC7zStkPKrZaHKS+XTQErP8OCio7+n8NmpbpAYK31nBw== +"@dhis2-ui/status-icon@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/status-icon/-/status-icon-9.2.0.tgz#5686721ef7dc1b1cd1cca315b11136e0809e568f" + integrity sha512-oILHs38xICDU27C7CXgFAEttU4QJzg0wImKlX4XVJ5z1aGeq6qOW0RUVibF6JtyAppEi9XsYZ+AB1KLEGnDFJw== dependencies: - "@dhis2-ui/loader" "8.12.4" + "@dhis2-ui/loader" "9.2.0" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" - "@dhis2/ui-icons" "8.12.4" + "@dhis2/ui-constants" "9.2.0" + "@dhis2/ui-icons" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/switch@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/switch/-/switch-8.12.4.tgz#856493c86e726696186ef986efcbae7ef6a53169" - integrity sha512-tjd3a+IKrneA5RqfYOasclPaYRRI378LtWvgi3wzjCL0+dXMBwRMDbDJZz+xH4SjMQryIfy/wcPalJfczPZ4xg== +"@dhis2-ui/switch@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/switch/-/switch-9.2.0.tgz#1fc9ed88546906da11aa19348b8ba8ff2e7c8414" + integrity sha512-IwVUiqxMKZmx7VtEJkyHqGzy4tvRNh4qbhmrFodaH6d9YrbycaFNECbphrT1OywrUBuci0Q2LovZhYKInkeIFg== dependencies: - "@dhis2-ui/field" "8.12.4" - "@dhis2-ui/required" "8.12.4" + "@dhis2-ui/field" "9.2.0" + "@dhis2-ui/required" "9.2.0" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" + "@dhis2/ui-constants" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/tab@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/tab/-/tab-8.12.4.tgz#3470cf4f5f2b529b6c0e592fd457d08234b48b83" - integrity sha512-AxZabWBQQL9fCB2j4J+P/8eCpKtY5DRyeDM8vEUdxaQQr5dUs7sGkDa8yB5uKyXKC4PdjR0Tpy/w8mE6bv+L3w== +"@dhis2-ui/tab@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/tab/-/tab-9.2.0.tgz#910223a907f8c007921924138306b53c2014f0fd" + integrity sha512-zVxwQ7WgjcrCGd+qWzLx+OtTWlGOIuC+AuSknHz0wpGgW3vawr1rMDA6j129l4O+ezzPs5bw8vN3xUQORbj0Bg== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" - "@dhis2/ui-icons" "8.12.4" + "@dhis2/ui-constants" "9.2.0" + "@dhis2/ui-icons" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/table@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/table/-/table-8.12.4.tgz#2a108f9fc96d2f45a2723b29dc71b5206f3b75f9" - integrity sha512-RNzXdzgsIWLQTU2aAyS6CZpKvIMTtigztSkv9XGdnpHtWp9EEeo//R1pbLYoNBLEohT8dqUiGdZC36ueeUbv1g== +"@dhis2-ui/table@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/table/-/table-9.2.0.tgz#83d535e6c60ff9c9c3f436e592489a131caef772" + integrity sha512-wep8wPQKRg5vYPGg3HLAvsAEo+lUk3L7wO7axHyzx/XuHR40QXRirpv4tsFVn8VPpSFVq0w/BFGp8ijp3Y6RtA== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" - "@dhis2/ui-icons" "8.12.4" + "@dhis2/ui-constants" "9.2.0" + "@dhis2/ui-icons" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/tag@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/tag/-/tag-8.12.4.tgz#2046e456a5b37214100088ad85a9babe56dc76c8" - integrity sha512-ja6nq/uxFaXwjYHtahPv9LEw+3w4ynmKMR1L6JQXEfX++s+/zdqt+TfvYQRICMNq6EJLy1fYWfUT104Nqr37GA== +"@dhis2-ui/tag@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/tag/-/tag-9.2.0.tgz#c6a11fbdfc20151cc75c323e6a6b1470ef064e58" + integrity sha512-qt/FUIFPZkghfF/fhhLHr4oOl54d1Zwy4CKf4cZmO2DSd09h7E7cfIUxXaX/shRHsSmeUhrFP2Nv5LAvu8XVUg== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" + "@dhis2/ui-constants" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/text-area@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/text-area/-/text-area-8.12.4.tgz#880919128623aaf304c3618382d9843625b34714" - integrity sha512-BiUPu1Lk5dBFkHRa/wB0XMzhC+uLkjmMiOde8N4LTpm5DS3P7TnkRp6hApeWasd3pGf2i9KUWqep+aJ0zilwVQ== +"@dhis2-ui/text-area@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/text-area/-/text-area-9.2.0.tgz#75f279281f1513801148f11c2693537a5e9beed7" + integrity sha512-KLBlerlO3OvBvzTVKKolfghaAMjDPUZkxFsyRvWEIPh1RRxS9ZprunXpBWRkNO2I6U6uwqN0DiBLTNbh/zrafg== dependencies: - "@dhis2-ui/box" "8.12.4" - "@dhis2-ui/field" "8.12.4" - "@dhis2-ui/loader" "8.12.4" - "@dhis2-ui/status-icon" "8.12.4" + "@dhis2-ui/box" "9.2.0" + "@dhis2-ui/field" "9.2.0" + "@dhis2-ui/loader" "9.2.0" + "@dhis2-ui/status-icon" "9.2.0" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" - "@dhis2/ui-icons" "8.12.4" + "@dhis2/ui-constants" "9.2.0" + "@dhis2/ui-icons" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/tooltip@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/tooltip/-/tooltip-8.12.4.tgz#a9eef55407cf4c5e4669d98ea9dc60303fa1153f" - integrity sha512-NUHPyDLYm1gLDNJkFmp5ch7sIKbDW/g68vE0tJQVHkun6buLWAfYeWcDZSyUsavzIDYutm1zyaepP4IS7ZD/9Q== +"@dhis2-ui/tooltip@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/tooltip/-/tooltip-9.2.0.tgz#e7a0827d9ca6b30b69073916f7801d01fb7c9793" + integrity sha512-7Gb/Occ5/Bju95dnxUGzt/Q4129zqGWrxu1+S2uhc0YPRSx83JcG0MivPsVsr0BeU+p+8xwTVdGBOhMmqLpL9Q== dependencies: - "@dhis2-ui/popper" "8.12.4" - "@dhis2-ui/portal" "8.12.4" + "@dhis2-ui/popper" "9.2.0" + "@dhis2-ui/portal" "9.2.0" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" + "@dhis2/ui-constants" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/transfer@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/transfer/-/transfer-8.12.4.tgz#f1ffc632051fb5a73a29ff54f8f58b257ea89dae" - integrity sha512-lT8D7NzLMJZdSlVGFQGjfos/3F1xE9Pt5fpNvs7hyafjFbAweE80VtIUwRDRUUsgYwUNVHXaxLHzDFrsyU1y9Q== +"@dhis2-ui/transfer@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/transfer/-/transfer-9.2.0.tgz#3624d08100b72143da5639ce231b53d57706ffdc" + integrity sha512-1+SdfeCBr+iOLaXf4gkpNLlysaBsWLOmlVzBdGVqFt4I4SMJrhFrCMUTz4A1Nsw7GpKGds35vGClQUe5LcuVSw== dependencies: - "@dhis2-ui/button" "8.12.4" - "@dhis2-ui/field" "8.12.4" - "@dhis2-ui/input" "8.12.4" - "@dhis2-ui/intersection-detector" "8.12.4" - "@dhis2-ui/loader" "8.12.4" + "@dhis2-ui/button" "9.2.0" + "@dhis2-ui/field" "9.2.0" + "@dhis2-ui/input" "9.2.0" + "@dhis2-ui/intersection-detector" "9.2.0" + "@dhis2-ui/loader" "9.2.0" "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" + "@dhis2/ui-constants" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2-ui/user-avatar@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2-ui/user-avatar/-/user-avatar-8.12.4.tgz#8a99554c7b5eba7aa0b704d89260dc211e43865f" - integrity sha512-NVfjI19+4C003RKZ3+VmyBISBpGShl0nfk9goy+AN3ByXEOZhlFo+3+8Rl7aY9ZkOU0cVeLiBMmBOqCmnzGGLg== +"@dhis2-ui/user-avatar@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2-ui/user-avatar/-/user-avatar-9.2.0.tgz#722b4bad239aff8eb2b639ca51bd6528a08da1e4" + integrity sha512-XRop5Mmc5q1GnrM3YgIEdjw0OX7/KA9ZdxRNS4AU7ifYMEjUNutYmq8a2bJ1M6eZfq2DrhRQui9/1E7MvK+Evw== dependencies: "@dhis2/prop-types" "^3.1.2" - "@dhis2/ui-constants" "8.12.4" + "@dhis2/ui-constants" "9.2.0" classnames "^2.3.1" prop-types "^15.7.2" -"@dhis2/analytics@^26.3.0": - version "26.3.0" - resolved "https://registry.yarnpkg.com/@dhis2/analytics/-/analytics-26.3.0.tgz#ada2fe27442f19704fa704e334546416c85ea1d6" - integrity sha512-B/pUh8K8wyivL4yBiwqPoQ94pMWwCqh0xu3Uak4jmJqS+jO0slUlyDLtAmXU/jqRlRgRg1nR4u18npjd511Q7A== +"@dhis2/analytics@999.9.9-outlier-table.alpha.4": + version "999.9.9-outlier-table.alpha.4" + resolved "https://registry.yarnpkg.com/@dhis2/analytics/-/analytics-999.9.9-outlier-table.alpha.4.tgz#a5ca4dc5cc8d1e819d9462c69a1d12149b9351c7" + integrity sha512-JtvJxqRQSEDoo2+Jq+bo0NIb2gq0hqCQfCpnTpET08uwkYFAwu6YKma2Z0rmK3A9IM0wwqSJNx3a7Cje7Px9BQ== dependencies: "@dhis2/d2-ui-rich-text" "^7.4.1" "@dhis2/multi-calendar-dates" "1.0.0" @@ -2286,91 +2286,91 @@ workbox-routing "^6.1.5" workbox-strategies "^6.1.5" -"@dhis2/ui-constants@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2/ui-constants/-/ui-constants-8.12.4.tgz#e805fc67e936fecd1b15b08cb033c5d83c761209" - integrity sha512-R/FzJaZauKfVa/VLeECrwTmGouS4USyDLXs/yLYUq/VJVr7laXZ2Ty29mOjgylaOTyxp0Mv/faj3zuheFEi/xg== +"@dhis2/ui-constants@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2/ui-constants/-/ui-constants-9.2.0.tgz#47506acaec5e4ce28630519a630a05657d2d29f6" + integrity sha512-gMbnVJQJmCPoGJHnY09BoDe6Z1vukzFdUcm0HPYyijs8ZWnclLs+69iVamxhskOnNWgj8hEt/FVs4mfhMcW3Cg== dependencies: prop-types "^15.7.2" -"@dhis2/ui-forms@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2/ui-forms/-/ui-forms-8.12.4.tgz#f64f3e8feacd6e85c291a6d2726eaf355372e2d2" - integrity sha512-atZwS7m5OBFFNdc0sVi3TgxVz/QGVA/v/MvpD091Xtl5m33Vox9MdcMXiGQ5qwXgqMnnWJZ/p/nheiRBFuJOVA== - dependencies: - "@dhis2-ui/button" "8.12.4" - "@dhis2-ui/checkbox" "8.12.4" - "@dhis2-ui/field" "8.12.4" - "@dhis2-ui/file-input" "8.12.4" - "@dhis2-ui/input" "8.12.4" - "@dhis2-ui/radio" "8.12.4" - "@dhis2-ui/select" "8.12.4" - "@dhis2-ui/switch" "8.12.4" - "@dhis2-ui/text-area" "8.12.4" +"@dhis2/ui-forms@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2/ui-forms/-/ui-forms-9.2.0.tgz#a5651dc5010a495c8a52ef5484e7d44b2583b715" + integrity sha512-eodiPW+ahR5wVsgrl/bFvj2zyeJD+DR9woqys4ZyoaHlKjOdeLqDNbJDnrS+AmHfte5uorF/aWzmEZr825LBVg== + dependencies: + "@dhis2-ui/button" "9.2.0" + "@dhis2-ui/checkbox" "9.2.0" + "@dhis2-ui/field" "9.2.0" + "@dhis2-ui/file-input" "9.2.0" + "@dhis2-ui/input" "9.2.0" + "@dhis2-ui/radio" "9.2.0" + "@dhis2-ui/select" "9.2.0" + "@dhis2-ui/switch" "9.2.0" + "@dhis2-ui/text-area" "9.2.0" "@dhis2/prop-types" "^3.1.2" classnames "^2.3.1" final-form "^4.20.2" prop-types "^15.7.2" react-final-form "^6.5.3" -"@dhis2/ui-icons@8.12.4": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2/ui-icons/-/ui-icons-8.12.4.tgz#0d5ee1e5a1e9e218d0a5cc9b3263d96ae908c4d5" - integrity sha512-4rFUTqhBEqCEmt9LFLTZCI3YOcu9wbMqrtKT22jFP5DzCfae6oYsYb7duFHshN7WeWt8qVDKn5xDwPLCKdm5Dg== - -"@dhis2/ui@^8.12.3", "@dhis2/ui@^8.7.6": - version "8.12.4" - resolved "https://registry.yarnpkg.com/@dhis2/ui/-/ui-8.12.4.tgz#0a3eb93db073ad7678a3b5669db43c05ee430c3d" - integrity sha512-ryc+0E0g7g460fYfRMuD0tE2gtiV+nJplZZnftWkpwnSuTwgGbvb9QZChtU679NDbrCDyJ4vPqV9ZemY+Vhj0Q== - dependencies: - "@dhis2-ui/alert" "8.12.4" - "@dhis2-ui/box" "8.12.4" - "@dhis2-ui/button" "8.12.4" - "@dhis2-ui/calendar" "8.12.4" - "@dhis2-ui/card" "8.12.4" - "@dhis2-ui/center" "8.12.4" - "@dhis2-ui/checkbox" "8.12.4" - "@dhis2-ui/chip" "8.12.4" - "@dhis2-ui/cover" "8.12.4" - "@dhis2-ui/css" "8.12.4" - "@dhis2-ui/divider" "8.12.4" - "@dhis2-ui/field" "8.12.4" - "@dhis2-ui/file-input" "8.12.4" - "@dhis2-ui/header-bar" "8.12.4" - "@dhis2-ui/help" "8.12.4" - "@dhis2-ui/input" "8.12.4" - "@dhis2-ui/intersection-detector" "8.12.4" - "@dhis2-ui/label" "8.12.4" - "@dhis2-ui/layer" "8.12.4" - "@dhis2-ui/legend" "8.12.4" - "@dhis2-ui/loader" "8.12.4" - "@dhis2-ui/logo" "8.12.4" - "@dhis2-ui/menu" "8.12.4" - "@dhis2-ui/modal" "8.12.4" - "@dhis2-ui/node" "8.12.4" - "@dhis2-ui/notice-box" "8.12.4" - "@dhis2-ui/organisation-unit-tree" "8.12.4" - "@dhis2-ui/pagination" "8.12.4" - "@dhis2-ui/popover" "8.12.4" - "@dhis2-ui/popper" "8.12.4" - "@dhis2-ui/portal" "8.12.4" - "@dhis2-ui/radio" "8.12.4" - "@dhis2-ui/required" "8.12.4" - "@dhis2-ui/segmented-control" "8.12.4" - "@dhis2-ui/select" "8.12.4" - "@dhis2-ui/selector-bar" "8.12.4" - "@dhis2-ui/sharing-dialog" "8.12.4" - "@dhis2-ui/switch" "8.12.4" - "@dhis2-ui/tab" "8.12.4" - "@dhis2-ui/table" "8.12.4" - "@dhis2-ui/tag" "8.12.4" - "@dhis2-ui/text-area" "8.12.4" - "@dhis2-ui/tooltip" "8.12.4" - "@dhis2-ui/transfer" "8.12.4" - "@dhis2-ui/user-avatar" "8.12.4" - "@dhis2/ui-constants" "8.12.4" - "@dhis2/ui-forms" "8.12.4" - "@dhis2/ui-icons" "8.12.4" +"@dhis2/ui-icons@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2/ui-icons/-/ui-icons-9.2.0.tgz#bddf5223cabec93c9499281b2181eb165346b639" + integrity sha512-g9993UGWVLwDcbV+wp3HqrK8AXFu49aped0GpZsQUlGbHIzEl1EgmjiII44N40VbXwVUnqIDmu99wBxpH5Gd+g== + +"@dhis2/ui@^8.12.3", "@dhis2/ui@^9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@dhis2/ui/-/ui-9.2.0.tgz#33d474cbc7cd95f8a714e019d6c69e144f77f86b" + integrity sha512-nhKwW5bmIfQvt3L16PffFO2NsDk9BgYb91vHx06fPgM56UdwGYSejpax8eU29vE9urmHSkijSpnBqY4buZy6Ow== + dependencies: + "@dhis2-ui/alert" "9.2.0" + "@dhis2-ui/box" "9.2.0" + "@dhis2-ui/button" "9.2.0" + "@dhis2-ui/calendar" "9.2.0" + "@dhis2-ui/card" "9.2.0" + "@dhis2-ui/center" "9.2.0" + "@dhis2-ui/checkbox" "9.2.0" + "@dhis2-ui/chip" "9.2.0" + "@dhis2-ui/cover" "9.2.0" + "@dhis2-ui/css" "9.2.0" + "@dhis2-ui/divider" "9.2.0" + "@dhis2-ui/field" "9.2.0" + "@dhis2-ui/file-input" "9.2.0" + "@dhis2-ui/header-bar" "9.2.0" + "@dhis2-ui/help" "9.2.0" + "@dhis2-ui/input" "9.2.0" + "@dhis2-ui/intersection-detector" "9.2.0" + "@dhis2-ui/label" "9.2.0" + "@dhis2-ui/layer" "9.2.0" + "@dhis2-ui/legend" "9.2.0" + "@dhis2-ui/loader" "9.2.0" + "@dhis2-ui/logo" "9.2.0" + "@dhis2-ui/menu" "9.2.0" + "@dhis2-ui/modal" "9.2.0" + "@dhis2-ui/node" "9.2.0" + "@dhis2-ui/notice-box" "9.2.0" + "@dhis2-ui/organisation-unit-tree" "9.2.0" + "@dhis2-ui/pagination" "9.2.0" + "@dhis2-ui/popover" "9.2.0" + "@dhis2-ui/popper" "9.2.0" + "@dhis2-ui/portal" "9.2.0" + "@dhis2-ui/radio" "9.2.0" + "@dhis2-ui/required" "9.2.0" + "@dhis2-ui/segmented-control" "9.2.0" + "@dhis2-ui/select" "9.2.0" + "@dhis2-ui/selector-bar" "9.2.0" + "@dhis2-ui/sharing-dialog" "9.2.0" + "@dhis2-ui/switch" "9.2.0" + "@dhis2-ui/tab" "9.2.0" + "@dhis2-ui/table" "9.2.0" + "@dhis2-ui/tag" "9.2.0" + "@dhis2-ui/text-area" "9.2.0" + "@dhis2-ui/tooltip" "9.2.0" + "@dhis2-ui/transfer" "9.2.0" + "@dhis2-ui/user-avatar" "9.2.0" + "@dhis2/ui-constants" "9.2.0" + "@dhis2/ui-forms" "9.2.0" + "@dhis2/ui-icons" "9.2.0" prop-types "^15.7.2" "@dnd-kit/accessibility@^3.0.0":