diff --git a/__tests__/gui/data_whitelist.feature b/__tests__/gui/data_whitelist.feature index 57e3896da..f30b3db5b 100644 --- a/__tests__/gui/data_whitelist.feature +++ b/__tests__/gui/data_whitelist.feature @@ -9,6 +9,7 @@ Feature: Data Whitelist And I click on "Demographics" in Data Mapper And I click on "Language" in Data Mapper And I click on "Language most spoken at home" in Data Mapper + And I confirm that available subindicators are "20-24,30-35" in Data Mapper And I click on "30-35" in Data Mapper And I expand the filter dialog Then I confirm that the choropleth is filtered by "gender:Female" at index 0 @@ -20,7 +21,7 @@ Feature: Data Whitelist # Rich data panel default filter When I expand Rich Data Panel - Then I confirm that "gender:Female" is applied to "Language most spoken at home" as a site-wide filter + Then I confirm that "gender:Female" is applied to "Language most spoken at home" Scenario: Restricting filter values and setting profile-wide filters on a view Given I am on the Wazimap Homepage Test View @@ -31,6 +32,7 @@ Feature: Data Whitelist And I click on "Demographics" in Data Mapper And I click on "Language" in Data Mapper And I click on "Language most spoken at home" in Data Mapper + And I confirm that available subindicators are "15-19,20-24,30-35" in Data Mapper And I click on "30-35" in Data Mapper And I expand the filter dialog Then I confirm that the choropleth is filtered by "gender:Male" at index 0 @@ -42,4 +44,4 @@ Feature: Data Whitelist # Rich data panel default filter When I expand Rich Data Panel - Then I confirm that "gender:Male" is applied to "Language most spoken at home" as a site-wide filter \ No newline at end of file + Then I confirm that "gender:Male" is applied to "Language most spoken at home" \ No newline at end of file diff --git a/__tests__/gui/data_whitelist/data_whitelist.js b/__tests__/gui/data_whitelist/data_whitelist.js index 0cbe96479..996aee155 100644 --- a/__tests__/gui/data_whitelist/data_whitelist.js +++ b/__tests__/gui/data_whitelist/data_whitelist.js @@ -1,10 +1,13 @@ import {Given, Then, When} from "cypress-cucumber-preprocessor/steps"; import { - collapseMyViewPanel, confirmChartIsFiltered, - confirmChoroplethIsFiltered, confirmDropdownOptions, + confirmChartIsFiltered, + confirmChoroplethIsFiltered, + confirmDropdownOptions, expandChoroplethFilterDialog, - expandDataMapper, expandRichDataPanel, - gotoHomepage, selectChoroplethDropdownOption, + expandDataMapper, + expandRichDataPanel, + gotoHomepage, + selectChoroplethDropdownOption, setupInterceptions, waitUntilGeographyIsLoaded } from "../common_cy_functions/general"; @@ -57,7 +60,7 @@ When('I expand Rich Data Panel', () => { expandRichDataPanel(); }) -Then(/^I confirm that "([^"]*)" is applied to "([^"]*)" as a site\-wide filter$/, function (filter, chartTitle) { +Then(/^I confirm that "([^"]*)" is applied to "([^"]*)"$/, function (filter, chartTitle) { const filters = filter.split(':'); confirmChartIsFiltered(filters[0], filters[1], chartTitle); }); @@ -66,3 +69,11 @@ Given('I am on the Wazimap Homepage Test View', () => { setupInterceptions(profiles, all_details, profile, themes, {}, [], profile_indicator_summary, profile_indicator_data); cy.visit("/?view=test"); }) + +When(/^I confirm that available subindicators are "([^"]*)" in Data Mapper$/, function (options) { + let optionsArr = options.split(','); + cy.get('.data-mapper').find('.subIndicator-item').should('have.length', optionsArr.length) + cy.get('.subIndicator-item').each(($div, index) => { + expect($div.text()).equal(optionsArr[index]); + }) +}); \ No newline at end of file diff --git a/__tests__/gui/data_whitelist/profile.json b/__tests__/gui/data_whitelist/profile.json index a8d11bb0a..540871bc0 100644 --- a/__tests__/gui/data_whitelist/profile.json +++ b/__tests__/gui/data_whitelist/profile.json @@ -37,6 +37,10 @@ "English", "Afrikaans", "This is ignored" + ], + "age": [ + "20-24", + "30-35" ] }, "views": { @@ -52,6 +56,11 @@ "English", "Sepedi", "isiXhosa" + ], + "age": [ + "15-19", + "20-24", + "30-35" ] } } diff --git a/__tests__/gui/data_whitelist/profile_indicator_summary.json b/__tests__/gui/data_whitelist/profile_indicator_summary.json index ab0f1f2cf..4af88a86e 100644 --- a/__tests__/gui/data_whitelist/profile_indicator_summary.json +++ b/__tests__/gui/data_whitelist/profile_indicator_summary.json @@ -5,6 +5,7 @@ "subcategories": { "Language": { "order": 242, + "id": 1, "name": "Language", "indicators": { "Language most spoken at home": { @@ -108,6 +109,7 @@ }, "Migration": { "order": 243, + "id": 2, "name": "Migration", "indicators": { "Region of birth": { @@ -211,6 +213,7 @@ }, "South African Citizenship": { "order": 246, + "id": 3, "name": "South African Citizenship", "indicators": { "Citizenship": { diff --git a/__tests__/gui/filter_comparison_snackbar/profile_default_filters/profile.json b/__tests__/gui/filter_comparison_snackbar/profile_default_filters/profile.json index ececd2fe1..f66e305a4 100644 --- a/__tests__/gui/filter_comparison_snackbar/profile_default_filters/profile.json +++ b/__tests__/gui/filter_comparison_snackbar/profile_default_filters/profile.json @@ -35,7 +35,8 @@ "age": [ "15-35 (ZA)", "15-24 (Intl)", - "30-35" + "30-35", + "15-19", ], "race": [ "Coloured", diff --git a/src/js/api.js b/src/js/api.js index 10cc1ee00..98b9f483b 100644 --- a/src/js/api.js +++ b/src/js/api.js @@ -14,6 +14,15 @@ export class API extends Observable { this.busyLoggingIn = false; this.failedLogins = 0; this.abortController = null; + this._restrictValues = {}; + } + + set restrictValues(value) { + this._restrictValues = value; + } + + get restrictValues() { + return this._restrictValues; } getToken() { @@ -33,6 +42,44 @@ export class API extends Observable { return this.loadUrl(url, this.abortController); } + getProfileWrapper(profileId, areaCode, version) { + const self = this; + + return self.getProfile(profileId, areaCode, version).then(data => { + Object.keys(data.profile.profile_data).forEach(categoryName => { + const subcategories = data.profile.profile_data[categoryName].subcategories; + Object.keys(subcategories).forEach(subcategoryName => { + const indicators = subcategories[subcategoryName].indicators; + if (indicators != null) { + Object.keys(indicators).forEach((indicator) => { + let indicatorData = indicators[indicator]; + + Object.keys(self.restrictValues).forEach(restrictKey => { + // does not contain the key + // or + // key value is one of the restrictValue + indicatorData.data = indicatorData.data.filter(x => Object.keys(x).indexOf(restrictKey) < 0 || + self.restrictValues[restrictKey].indexOf(x[restrictKey]) >= 0); + + // filter metadata + indicatorData.metadata.groups = indicatorData.metadata.groups.map(group => { + if (group.name === restrictKey) { + group.subindicators = group.subindicators.filter(element => self.restrictValues[restrictKey].includes(element)); + } + + return group; + }) + + }) + }); + } + }) + }) + + return data; + }); + } + getProfileWithoutVersion(profileId, areaCode) { const url = `${this.baseUrl}/all_details/profile/${profileId}/geography/${areaCode}/?skip-children=true&format=json`; return this.loadUrl(url, this.abortController); @@ -53,6 +100,24 @@ export class API extends Observable { return this.loadUrl(url, this.abortController); } + getIndicatorChildDataWrapper(profileId, areaCode, indicatorId) { + const self = this; + + return self.getIndicatorChildData(profileId, areaCode, indicatorId).then(data => { + Object.keys(data).forEach((geo) => { + Object.keys(self.restrictValues).forEach(restrictKey => { + // does not contain the key + // or + // key value is one of the restrictValue + data[geo] = data[geo].filter(x => Object.keys(x).indexOf(restrictKey) < 0 + || self.restrictValues[restrictKey].indexOf(x[restrictKey]) >= 0); + }) + }); + + return data; + }); + } + loadThemes(profileId) { const url = `${this.baseUrl}/profile/${profileId}/points/themes/?format=json`; return this.loadUrl(url); @@ -193,6 +258,40 @@ export class API extends Observable { return this.loadUrl(url, this.abortController); } + async getIndicatorSummaryWrapper(profileId, areaCode, version) { + const self = this; + + return self.getIndicatorSummary(profileId, areaCode, version).then(data => { + Object.keys(data).forEach((category) => { + let categoryData = data[category]; + let subCategories = categoryData.subcategories; + + Object.keys(subCategories).forEach((subCategory) => { + let subCategoryData = subCategories[subCategory]; + let indicators = subCategoryData.indicators; + + if (indicators != null) { + Object.keys(indicators).forEach((indicator) => { + let indicatorData = indicators[indicator]; + + Object.keys(self.restrictValues).forEach(restrictKey => { + indicatorData.metadata.groups = indicatorData.metadata.groups.map(group => { + if (group.name === restrictKey) { + group.subindicators = group.subindicators.filter(element => self.restrictValues[restrictKey].includes(element)); + } + + return group; + }) + }) + }); + } + }); + }); + + return data; + }); + } + cancelAndInitAbortController() { if (this.abortController !== null) { //on first request this.abortController is null diff --git a/src/js/elements/data_mapper/components/indicator_tree_view.js b/src/js/elements/data_mapper/components/indicator_tree_view.js index f6ba29727..1db5b078f 100644 --- a/src/js/elements/data_mapper/components/indicator_tree_view.js +++ b/src/js/elements/data_mapper/components/indicator_tree_view.js @@ -4,12 +4,12 @@ import { StyledCategoryTreeItem, StyledSubCategoryTreeItem, StyledSubindicatorTreeItem, - StyledIndicatorTreeItem + StyledIndicatorTreeItem, + StyledNoSubindicatorTreeItem } from "./styledElements"; import Box from "@mui/material/Box"; import {Typography} from "@mui/material"; -import VisibilityIcon from '@mui/icons-material/Visibility'; -import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'; +import {checkIfSubIndicatorHasChildren} from "../../../utils"; const LoadingItemView = (props) => { @@ -24,6 +24,38 @@ const LoadingItemView = (props) => { ) } +const getSubIndicators = (indicator) => { + const primaryGroup = indicator.metadata.primary_group; + const primaryGroupObj = indicator.metadata.groups.filter( + group => group.name === primaryGroup + ) + + if (primaryGroupObj.length > 0) { + return primaryGroupObj[0].subindicators.filter( + sub => sub !== undefined && sub + ); + } + return []; +} + +const NoSubindicatorView = (props) => { + return ( + + + No data available for this indicator in the current view. + + + } + data-test-id={`datamapper-subindicator-${props.indicator.id}-not-available`} + className={"subIndicator-item"} + /> + ) +} + const SubindicatorItemView = (props) => { const onClickSubindicator = useCallback( () => { @@ -76,17 +108,7 @@ const IndicatorItemView = (props) => { const subindicators = useMemo( () => { const indicator = props.indicator; - const primaryGroup = indicator.metadata.primary_group; - const primaryGroupObj = indicator.metadata.groups.filter( - group => group.name === primaryGroup - ) - - if (primaryGroupObj.length > 0) { - return primaryGroupObj[0].subindicators.filter( - sub => sub !== undefined && sub - ); - } - return []; + return getSubIndicators(indicator); }, [ props.indicator ] @@ -101,7 +123,7 @@ const IndicatorItemView = (props) => { useEffect(() => { if (!props.indicator.isHidden && props.indicator?.indicatorData === undefined && !loading) { setLoading(true); - props.api.getIndicatorChildData( + props.api.getIndicatorChildDataWrapper( props.controller.state.profileId, props.controller.state.profile.profile.geography.code, props.indicator.id @@ -117,41 +139,58 @@ const IndicatorItemView = (props) => { } ); - return ( - - - {props.indicator.label} - - - } data-test-id={`datamapper-indicator-${props.indicator.id}`}> - {loading && } - {!loading && subindicators.length > 0 && subindicators.map( - (subindicator, index) => { - return ( - - ) - }) - } - - ) + const checkForSubIndicatorData = () => { + let isValid = false; + let indicatorData = props.indicator?.indicatorData; + if (indicatorData !== undefined) { + isValid = Object.keys(indicatorData).some((geo) => { + return indicatorData[geo].length > 0; + }) + } + + return isValid; + } + + if (subindicators.length > 0) { + return ( + + + {props.indicator.label} + + + } data-test-id={`datamapper-indicator-${props.indicator.id}`}> + {loading && } + {!loading && checkForSubIndicatorData() && subindicators.map( + (subindicator, index) => { + return ( + + ) + }) + } + {!loading && !checkForSubIndicatorData() && } + + ) + } } const IndicatorSubCategoryTreeView = (props) => { - const [indicators, setIndicators] = useState(props.subcategory.indicators); + const [indicators, setIndicators] = useState(props.subcategory.indicators.filter(x => getSubIndicators(x).length > 0)); const handleIndicatorChange = (indicator) => { let newArr = indicators.map(ni => { @@ -163,40 +202,41 @@ const IndicatorSubCategoryTreeView = (props) => { setIndicators(newArr) } - return ( - - - {props.subcategory.name} - - - } data-test-id={`datamapper-subcategory-${props.subcategory.id}`}> - {!props.subcategory.length > 0 && indicators != null && indicators.map( - (indicator, index) => { - - if (!indicator.isHidden) { - return ( - handleIndicatorChange(indicator)} - key={`datamapper-indicator-${indicator.id}-${index}-${props.controller.state.profile.profile.geography.code}`} - api={props.api} - controller={props.controller} - categoryName={props.categoryName} - SubCategoryName={props.subcategory.name} - parents={{ - ...props.parents, - subcategory: props.subcategory.name - }} - /> - ) - } - }) - } - + if (indicators != null && indicators.length > 0) { + return ( + + + {props.subcategory.name} + + + } data-test-id={`datamapper-subcategory-${props.subcategory.id}`}> + {!props.subcategory.length > 0 && indicators != null && indicators.map( + (indicator, index) => { - ) + if (!indicator.isHidden) { + return ( + handleIndicatorChange(indicator)} + key={`datamapper-indicator-${indicator.id}-${index}-${props.controller.state.profile.profile.geography.code}`} + api={props.api} + controller={props.controller} + categoryName={props.categoryName} + SubCategoryName={props.subcategory.name} + parents={{ + ...props.parents, + subcategory: props.subcategory.name + }} + /> + ) + } + }) + } + + ) + } } const IndicatorCategoryTreeView = (props) => { diff --git a/src/js/elements/data_mapper/components/styledElements.js b/src/js/elements/data_mapper/components/styledElements.js index 35c90c0c5..7c619534c 100644 --- a/src/js/elements/data_mapper/components/styledElements.js +++ b/src/js/elements/data_mapper/components/styledElements.js @@ -1,260 +1,276 @@ import React from 'react'; -import { styled } from '@mui/system'; +import {styled} from '@mui/system'; import SvgIcon from '@mui/material/SvgIcon'; import Box from '@mui/material/Box'; -import TreeItem, { treeItemClasses } from '@mui/lab/TreeItem'; +import TreeItem, {treeItemClasses} from '@mui/lab/TreeItem'; export const StyledCategoryTreeItem = styled(TreeItem)(() => ({ - [`& .${treeItemClasses.content}`]: { - 'flexDirection': 'row-reverse', - 'marginBottom': '8px', - 'backgroundColor': '#39ad84', - 'borderRadius': '2px', - 'height': '36px', - 'paddingLeft': '8px', - 'transition': 'all .2s ease', - 'color': '#fff', - 'textDecoration': 'none', - 'cursor': 'pointer', - 'fontFamily': 'Roboto,sans-serif', - '&:hover': { - 'backgroundColor': '#39ad84', + [`& .${treeItemClasses.content}`]: { + 'flexDirection': 'row-reverse', + 'marginBottom': '8px', + 'backgroundColor': '#39ad84', + 'borderRadius': '2px', + 'height': '36px', + 'paddingLeft': '8px', + 'transition': 'all .2s ease', + 'color': '#fff', + 'textDecoration': 'none', + 'cursor': 'pointer', + 'fontFamily': 'Roboto,sans-serif', + '&:hover': { + 'backgroundColor': '#39ad84', + }, + '&.Mui-focused, &.Mui-selected, &.Mui-selected.Mui-focused': { + 'backgroundColor': '#39ad84', + }, + '& .MuiTreeItem-iconContainer svg': { + fontSize: '22px' + }, + '& .MuiTreeItem-label': { + paddingLeft: '0px', + }, + '& .MuiBox-root': { + padding: '0px', + }, + [`& .${treeItemClasses.label}`]: { + 'width': '100%', + 'whiteSpace': 'nowrap', + 'overflow': 'hidden', + 'textOverflow': 'ellipsis', + 'fontSize': '1em', + 'fontWeight': '500', + 'letterSpacing': '.3px', + 'marginRight': '12px', + 'paddingLeft': '0px', + }, }, - '&.Mui-focused, &.Mui-selected, &.Mui-selected.Mui-focused': { - 'backgroundColor': '#39ad84', - }, - '& .MuiTreeItem-iconContainer svg': { - fontSize: '22px' - }, - '& .MuiTreeItem-label': { - paddingLeft: '0px', - }, - '& .MuiBox-root': { - padding: '0px', - }, - [`& .${treeItemClasses.label}`]: { - 'width': '100%', - 'whiteSpace': 'nowrap', - 'overflow': 'hidden', - 'textOverflow': 'ellipsis', - 'fontSize': '1em', - 'fontWeight': '500', - 'letterSpacing': '.3px', - 'marginRight': '12px', - 'paddingLeft': '0px', - }, - }, - [`& .${treeItemClasses.group}`]: { - marginLeft: '12px', - } + [`& .${treeItemClasses.group}`]: { + marginLeft: '12px', + } })); export const StyledSubCategoryTreeItem = styled(TreeItem)(() => ({ - [`& .${treeItemClasses.content}`]: { - 'flexDirection': 'row-reverse', - 'backgroundColor': '#f0f0f0', - 'borderRadius': '2px', - 'marginBottom': '6px', - 'height': '32px', - 'paddingLeft': '8px', - 'transition': 'all .2s ease', - 'textDecoration': 'none', - 'cursor': 'pointer', - 'fontFamily': 'Roboto,sans-serif', - '&:hover': { - 'backgroundColor': '#f0f0f0', - }, - '&.Mui-focused, &.Mui-selected, &.Mui-selected.Mui-focused': { - 'backgroundColor': '#f0f0f0', + [`& .${treeItemClasses.content}`]: { + 'flexDirection': 'row-reverse', + 'backgroundColor': '#f0f0f0', + 'borderRadius': '2px', + 'marginBottom': '6px', + 'height': '32px', + 'paddingLeft': '8px', + 'transition': 'all .2s ease', + 'textDecoration': 'none', + 'cursor': 'pointer', + 'fontFamily': 'Roboto,sans-serif', + '&:hover': { + 'backgroundColor': '#f0f0f0', + }, + '&.Mui-focused, &.Mui-selected, &.Mui-selected.Mui-focused': { + 'backgroundColor': '#f0f0f0', + }, + '& .MuiTreeItem-iconContainer svg': { + fontSize: '18px', + color: '#666' + }, + '& .MuiTreeItem-label': { + paddingLeft: '0px', + }, + '& .MuiBox-root': { + padding: '0px', + }, + [`& .${treeItemClasses.label}`]: { + 'width': '100%', + 'whiteSpace': 'nowrap', + 'overflow': 'hidden', + 'textOverflow': 'ellipsis', + 'fontSize': '.85em', + 'fontWeight': '500', + 'letterSpacing': '.3px', + 'marginRight': '12px', + 'paddingLeft': '0px', + 'color': '#666' + }, }, - '& .MuiTreeItem-iconContainer svg': { - fontSize: '18px', - color: '#666' - }, - '& .MuiTreeItem-label': { - paddingLeft: '0px', - }, - '& .MuiBox-root': { - padding: '0px', - }, - [`& .${treeItemClasses.label}`]: { - 'width': '100%', - 'whiteSpace': 'nowrap', - 'overflow': 'hidden', - 'textOverflow': 'ellipsis', - 'fontSize': '.85em', - 'fontWeight': '500', - 'letterSpacing': '.3px', - 'marginRight': '12px', - 'paddingLeft': '0px', - 'color': '#666' - }, - }, - [`& .${treeItemClasses.group}`]: { - marginLeft: '12px', - } + [`& .${treeItemClasses.group}`]: { + marginLeft: '12px', + } })); export const StyledSubindicatorTreeItem = styled(TreeItem)(() => ({ - [`& .${treeItemClasses.content}`]: { - 'flexDirection': 'row-reverse', - 'backgroundColor': '#f0f0f0', - 'borderRadius': '2px', - 'marginBottom': '6px', - 'height': '32px', - 'paddingLeft': '8px', - 'transition': 'all .2s ease', - 'textDecoration': 'none', - 'cursor': 'pointer', - 'fontFamily': 'Roboto,sans-serif', - '&:hover': { - 'backgroundColor': '#dad7d7', - }, - '&.Mui-focused, &.Mui-selected, &.Mui-selected.Mui-focused': { - 'backgroundColor': '#dad7d7', - }, - '& .MuiTreeItem-iconContainer svg': { - fontSize: '18px', - color: '#666' + [`& .${treeItemClasses.content}`]: { + 'flexDirection': 'row-reverse', + 'backgroundColor': '#f0f0f0', + 'borderRadius': '2px', + 'marginBottom': '6px', + 'height': '32px', + 'paddingLeft': '8px', + 'transition': 'all .2s ease', + 'textDecoration': 'none', + 'cursor': 'pointer', + 'fontFamily': 'Roboto,sans-serif', + '&:hover': { + 'backgroundColor': '#dad7d7', + }, + '&.Mui-focused, &.Mui-selected, &.Mui-selected.Mui-focused': { + 'backgroundColor': '#dad7d7', + }, + '& .MuiTreeItem-iconContainer svg': { + fontSize: '18px', + color: '#666' + }, + '& .MuiTreeItem-label': { + paddingLeft: '0px', + }, + '& .MuiBox-root': { + padding: '0px', + }, + '&::after': { + position: 'absolute', + 'left': '24px', + 'zIndex': '1', + 'width': '8px', + 'height': '1px', + marginLeft: '4px', + backgroundColor: 'rgba(0, 0, 0, 0.1)', + content: '""' + }, + [`& .${treeItemClasses.label}`]: { + 'width': '100%', + 'whiteSpace': 'nowrap', + 'overflow': 'hidden', + 'textOverflow': 'ellipsis', + 'fontSize': '.85em', + 'fontWeight': '500', + 'letterSpacing': '.3px', + 'marginRight': '12px', + 'paddingLeft': '0px', + 'color': '#666' + }, }, - '& .MuiTreeItem-label': { - paddingLeft: '0px', - }, - '& .MuiBox-root': { - padding: '0px', - }, - '&::after': { - position: 'absolute', - 'left': '24px', - 'zIndex': '1', - 'width': '8px', - 'height': '1px', - marginLeft: '4px', - backgroundColor: 'rgba(0, 0, 0, 0.1)', - content: '""' - }, - [`& .${treeItemClasses.label}`]: { - 'width': '100%', - 'whiteSpace': 'nowrap', - 'overflow': 'hidden', - 'textOverflow': 'ellipsis', - 'fontSize': '.85em', - 'fontWeight': '500', - 'letterSpacing': '.3px', - 'marginRight': '12px', - 'paddingLeft': '0px', - 'color': '#666' - }, - }, - [`& .${treeItemClasses.group}`]: { - marginLeft: '12px' - } + [`& .${treeItemClasses.group}`]: { + marginLeft: '12px' + } +})); + +export const StyledNoSubindicatorTreeItem = styled(StyledSubindicatorTreeItem)(() => ({ + [`& .${treeItemClasses.content}`]: { + 'height': '56px', + 'cursor': 'unset', + '&:hover': { + 'backgroundColor': '#f0f0f0', + }, + '&::after': { + backgroundColor: 'unset', + }, + [`& .${treeItemClasses.label}`]: { + 'whiteSpace': 'normal', + } + } })); export const StyledIndicatorTreeItem = styled(TreeItem)(() => ({ - [`& .${treeItemClasses.content}`]: { - 'flexDirection': 'row-reverse', - 'backgroundColor': '#f0f0f0', - 'borderRadius': '2px', - 'marginBottom': '6px', - 'height': '32px', - 'paddingLeft': '8px', - 'transition': 'all .2s ease', - 'textDecoration': 'none', - 'cursor': 'pointer', - 'fontFamily': 'Roboto,sans-serif', - '&:hover': { - 'backgroundColor': '#f0f0f0', - }, - '&.Mui-focused, &.Mui-selected, &.Mui-selected.Mui-focused': { - 'backgroundColor': '#f0f0f0', - }, - '& .MuiTreeItem-iconContainer svg': { - fontSize: '18px', - color: '#666' - }, - '& .MuiTreeItem-label': { - paddingLeft: '0px', - }, - '& .MuiBox-root': { - padding: '0px', - }, - [`& .${treeItemClasses.label}`]: { - 'width': '100%', - 'whiteSpace': 'nowrap', - 'overflow': 'hidden', - 'textOverflow': 'ellipsis', - 'fontSize': '.85em', - 'fontWeight': '500', - 'letterSpacing': '.3px', - 'marginRight': '12px', - 'paddingLeft': '0px', - 'color': '#666' + [`& .${treeItemClasses.content}`]: { + 'flexDirection': 'row-reverse', + 'backgroundColor': '#f0f0f0', + 'borderRadius': '2px', + 'marginBottom': '6px', + 'height': '32px', + 'paddingLeft': '8px', + 'transition': 'all .2s ease', + 'textDecoration': 'none', + 'cursor': 'pointer', + 'fontFamily': 'Roboto,sans-serif', + '&:hover': { + 'backgroundColor': '#f0f0f0', + }, + '&.Mui-focused, &.Mui-selected, &.Mui-selected.Mui-focused': { + 'backgroundColor': '#f0f0f0', + }, + '& .MuiTreeItem-iconContainer svg': { + fontSize: '18px', + color: '#666' + }, + '& .MuiTreeItem-label': { + paddingLeft: '0px', + }, + '& .MuiBox-root': { + padding: '0px', + }, + [`& .${treeItemClasses.label}`]: { + 'width': '100%', + 'whiteSpace': 'nowrap', + 'overflow': 'hidden', + 'textOverflow': 'ellipsis', + 'fontSize': '.85em', + 'fontWeight': '500', + 'letterSpacing': '.3px', + 'marginRight': '12px', + 'paddingLeft': '0px', + 'color': '#666' + }, }, - }, - [`& .${treeItemClasses.group}`]: { - marginLeft: '12px', - borderLeft: '1px solid rgba(0, 0, 0, 0.11)', - marginLeft: '0px', - paddingLeft: '11px', - marginLeft: '3px', - } + [`& .${treeItemClasses.group}`]: { + marginLeft: '12px', + borderLeft: '1px solid rgba(0, 0, 0, 0.11)', + marginLeft: '0px', + paddingLeft: '11px', + marginLeft: '3px', + } })); -export const ParentContainer = styled(Box)(({ theme }) => ({ - display: 'grid', - gridAutoColumns: '1fr', - borderRadius: 2, - backgroundColor: '#f0f0f0', - padding: '6px', - marginTop: '20px', - paddingLeft: '0px', +export const ParentContainer = styled(Box)(({theme}) => ({ + display: 'grid', + gridAutoColumns: '1fr', + borderRadius: 2, + backgroundColor: '#f0f0f0', + padding: '6px', + marginTop: '20px', + paddingLeft: '0px', })); -export const IconContainer = styled(Box)(({ theme }) => ({ - gridRow: '1', - gridColumn: '1/2' +export const IconContainer = styled(Box)(({theme}) => ({ + gridRow: '1', + gridColumn: '1/2' })); -export const TextContainer = styled(Box)(({ theme }) => ({ - gridRow: '1', - gridColumn: 'span 5', - '& p': { - color: '#666', - fontSize: '0.9em', - fontWeight: '500', - lineHeight: '20px', - marginBottom: '0' - } +export const TextContainer = styled(Box)(({theme}) => ({ + gridRow: '1', + gridColumn: 'span 5', + '& p': { + color: '#666', + fontSize: '0.9em', + fontWeight: '500', + lineHeight: '20px', + marginBottom: '0' + } })); -export const Link = styled('a')(({ theme }) => ({ - color: '#39ad84' +export const Link = styled('a')(({theme}) => ({ + color: '#39ad84' })); const PanelIcon = (props) => { - return ( - - - - ); + return ( + + + + ); } -export const PanelIconSidebar = styled(PanelIcon)(({ theme }) => ({ - fontSize: '30px', - margin: 'auto', - display: 'block', - marginTop: '5px', +export const PanelIconSidebar = styled(PanelIcon)(({theme}) => ({ + fontSize: '30px', + margin: 'auto', + display: 'block', + marginTop: '5px', })); -export const PanelIconLink = styled(PanelIcon)(({ theme }) => ({ - fontSize: '18px', - verticalAlign: 'bottom', - color: '#39ad84' +export const PanelIconLink = styled(PanelIcon)(({theme}) => ({ + fontSize: '18px', + verticalAlign: 'bottom', + color: '#39ad84' })); diff --git a/src/js/index.js b/src/js/index.js index f9fa62090..3f7d39208 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -110,11 +110,13 @@ async function init() { const errorNotifier = new ErrorNotifier(); errorNotifier.registerErrorHandler(); - const api = new API(pc.baseUrl, hostname); + const api = new API(pc.baseUrl, pc.config); const data = await api.getProfileConfiguration(hostname); pc.config.setConfig(data.configuration || {}) pc.config.setVersions(data.geography_hierarchy || {}) + api.restrictValues = pc.config.restrictValues; + pc.config.api = api; pc.profile = data.id; pc.config.baseUrl = pc.baseUrl; diff --git a/src/js/models/data_filter.js b/src/js/models/data_filter.js index 663831586..1c02832ed 100644 --- a/src/js/models/data_filter.js +++ b/src/js/models/data_filter.js @@ -1,14 +1,10 @@ export class DataFilter { - constructor(group, restrictValues, keys = { + constructor(group, keys = { name: 'name', values: 'subindicators' }) { this._name = group[keys.name]; - let values = group[keys.values]; - if (restrictValues[group[keys.name]] !== undefined) { - values = values.filter(element => restrictValues[group[keys.name]].includes(element)); - } - this._values = values; + this._values = group[keys.values]; this._can_aggregate = group.can_aggregate; } diff --git a/src/js/models/data_filter_model.js b/src/js/models/data_filter_model.js index e9eaaac00..44ec57968 100644 --- a/src/js/models/data_filter_model.js +++ b/src/js/models/data_filter_model.js @@ -90,7 +90,7 @@ export class DataFilterModel extends Observable { let self = this; let gr = {}; this.groups.forEach(group => { - let dataFilter = new DataFilter(group, this.restrictValues, self.keys); + let dataFilter = new DataFilter(group, self.keys); gr[dataFilter[this.keys.name]] = dataFilter; }); @@ -101,7 +101,7 @@ export class DataFilterModel extends Observable { let self = this; let filters = []; this.groups.forEach(group => { - let dataFilter = new DataFilter(group, this.restrictValues, self.keys); + let dataFilter = new DataFilter(group, self.keys); filters.push(dataFilter); }); diff --git a/src/js/profile/blocks/indicator.js b/src/js/profile/blocks/indicator.js index 2913e1856..4b9f509e0 100644 --- a/src/js/profile/blocks/indicator.js +++ b/src/js/profile/blocks/indicator.js @@ -17,6 +17,7 @@ export class Indicator extends ContentBlock { hiddenIndicators = [] ) { super(parent, container, indicator, title, isLast, geography, hiddenIndicators); + this.chartAttribution = chartAttribution; this._chart = null; this.prepareDomElements(); @@ -63,6 +64,7 @@ export class Indicator extends ContentBlock { const configuration = this.indicator.chartConfiguration; let chartData = this.orderChartData(); + let c = new Chart(this, configuration, chartData, groups, this.container, this.title, this.chartAttribution, addLockButton, restrictValues, defaultFilters); this.bubbleEvents(c, [ 'profile.chart.saveAsPng', 'profile.chart.valueTypeChanged', diff --git a/src/js/profile/subcategory.js b/src/js/profile/subcategory.js index 203559b40..ede2c1201 100644 --- a/src/js/profile/subcategory.js +++ b/src/js/profile/subcategory.js @@ -101,19 +101,19 @@ export class Subcategory extends Component { } set hasKeyMetrics(value) { - this._hasKeyMetrics=value; + this._hasKeyMetrics = value; } updateVisibility = () => { - this.isVisible = Object.values(this._indicators).filter( - indicator => indicator.isVisible - ).length > 0 || this.hasKeyMetrics; - this.parent.updateVisibility(); + this.isVisible = Object.values(this._indicators).filter( + indicator => indicator.isVisible + ).length > 0 || this.hasKeyMetrics; + this.parent.updateVisibility(); } updateDomElements = () => { - $(this._scHeader).parents('.section').find(subcategoryHeaderClass).removeClass('first'); - $(this._scHeader).removeClass('page-break-before').addClass('first'); + $(this._scHeader).parents('.section').find(subcategoryHeaderClass).removeClass('first'); + $(this._scHeader).removeClass('page-break-before').addClass('first'); } addSubCategoryHeaders = (wrapper, subcategory, detail, isFirst) => { @@ -181,7 +181,7 @@ export class Subcategory extends Component { if (!isEmpty) { for (const indicator of sortBy(detail.indicators, "order")) { const title = Object.keys(detail.indicators).filter(k => detail.indicators[k] === indicator)[0]; - if (typeof indicator.data !== 'undefined') { + if (typeof indicator.data !== 'undefined' && indicator.data.length > 0) { let isLast = index === lastIndex; let block = null; diff --git a/src/js/versions/version_controller.js b/src/js/versions/version_controller.js index 3fed1fc60..5525f4d9a 100644 --- a/src/js/versions/version_controller.js +++ b/src/js/versions/version_controller.js @@ -245,7 +245,7 @@ export class VersionController extends Component { const areaCode = payload.payload.areaCode; this.versions.forEach((version) => { - const promise = this.api.getIndicatorSummary(profileId, areaCode, version.model.name) + const promise = this.api.getIndicatorSummaryWrapper(profileId, areaCode, version.model.name) .then((data) => { const childrenIndicators = new ChildrenIndicators(data); @@ -352,7 +352,7 @@ export class VersionController extends Component { } getAllDetails(version) { - const promise = this.api.getProfile(this.profileId, this.areaCode, version.model.name).then(js => { + const promise = this.api.getProfileWrapper(this.profileId, this.areaCode, version.model.name).then(js => { version.model.exists = true; this.versionsRawData.push({ 'version': version,