Skip to content

Commit

Permalink
feat: dashboards filters (#275)
Browse files Browse the repository at this point in the history
Fixes: DHIS2-4881, DHIS2-4882, DHIS2-4883, DHIS2-3455

This PR implements dashboard filters for ou, pe and dynamic dimensions.
It uses shared analytics components.

Details: 
* Rename action/reducer to itemFilters

Dashboards app can have multiple filters/overrides

* Fetch list of dimensions at app start and store it

The list does not really change, so we can store it once at app startup
and use it in the different components, also shared ones

* Add reducer/actions for editItemFilters

This is used to differentiate between applied filters (itemFilters in
the store) and selected items in the filters, which is needed by the
dimension dialogs when the user interacts with the "Add filter" feature
in the app.
The Confirm button is what copies the selected filters in itemFilters
and that is what is used by the app for applying the filters to the
items.

* Import reducer/actions from the renamed itemFilters

* Add new components for dashboard item filtering

* Use new FilterSelector component

* Fix tests for itemFilters reducer

* Ensure proper URL encoding

Pass the query string object as argument to Api.get() instead of
including it directly in the URL

* Add edit and remove reducer/action for dashboard filters

* Add component for showing active filter badges

* Enable FilterBar under TitleBar

* Fix filters handling. Remove debug logs

* Apply dashboard filters to AO before passing it to plugins

* Add reselect dependency

* Add new strings for translations

* Move FilterBadge in its own file. Add click handler.

When clicking on the badge, it should open the corresponding filter
dialog for editing the filter.

* Apply only the filter currently edited

The Confirm button in FilterDialog only applies the currently edited
filter instead of copying all the edited filters.
The Cancel button only closes the dialog, but keeps the filter selection
in the store (in editItemFilters).

* Pass dashboard filters in AO to plugins

* Style buttons in view mode and filter dialogs

* Pass around displayNameProperty used in the org unit tree

* Open the filter dialog when clicking on the filter badge

This allows to edit the filter

* Fix tests

* Remove unused code and dependency

* Remove d2-ui dependency

* Enable rich text editor/parser on Text dashboard items

* Make sure the full AO is preserved when switching between edit/view

* Add @dhis2/d2-ui-analytics dependency for shared components

* Update translations pot file

* Update yarn.lock  after updates in data-visualizer-plugin

* Fix failing tests

* test fixes

* upgrade d2-ui-analytics

* test fix
  • Loading branch information
edoardo authored and jenniferarnesen committed Apr 9, 2019
1 parent a288aba commit bc22817
Show file tree
Hide file tree
Showing 42 changed files with 1,350 additions and 551 deletions.
27 changes: 25 additions & 2 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -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: 2019-04-02T12:31:20.966Z\n"
"PO-Revision-Date: 2019-04-02T12:31:20.966Z\n"
"POT-Creation-Date: 2019-04-08T11:33:46.174Z\n"
"PO-Revision-Date: 2019-04-08T11:33:46.174Z\n"

msgid "Dashboard"
msgstr ""
Expand Down Expand Up @@ -53,6 +53,14 @@ msgstr ""
msgid "Requested dashboard not found"
msgstr ""

msgid "{{count}} selected"
msgid_plural "{{count}} selected"
msgstr[0] ""
msgstr[1] ""

msgid "Remove"
msgstr ""

msgid "Messages"
msgstr ""

Expand All @@ -77,6 +85,15 @@ msgstr ""
msgid "No data to display"
msgstr ""

msgid "Cancel"
msgstr ""

msgid "Confirm"
msgstr ""

msgid "Add filter"
msgstr ""

msgid "There are no items on this dashboard"
msgstr ""

Expand Down Expand Up @@ -110,6 +127,12 @@ msgstr ""
msgid "No description"
msgstr ""

msgid "Edit"
msgstr ""

msgid "Share"
msgstr ""

msgid "Pivot tables"
msgstr ""

Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@
"license": "BSD-3-Clause",
"dependencies": {
"@dhis2/d2-i18n": "^1.0.4",
"@dhis2/d2-ui-analytics": "0.0.4",
"@dhis2/d2-ui-core": "^5.2.9",
"@dhis2/d2-ui-interpretations": "^5.2.10",
"@dhis2/d2-ui-mentions-wrapper": "^5.2.9",
"@dhis2/d2-ui-org-unit-tree": "^5.2.9",
"@dhis2/d2-ui-rich-text": "^5.2.9",
"@dhis2/d2-ui-rich-text": "^5.3.0",
"@dhis2/d2-ui-sharing-dialog": "^5.2.9",
"@dhis2/d2-ui-translation-dialog": "^5.2.9",
"@dhis2/ui": "1.0.0-beta.15",
"@dhis2/ui-core": "^1.1.3",
"@material-ui/core": "^3.9.2",
"@material-ui/icons": "^3.0.2",
"d2": "^31.6.0",
"d2-ui": "^29.0.30",
"d2-utilizr": "^0.2.16",
"data-visualizer-plugin": "github:d2-ci/data-visualizer-plugin",
"i18next": "^15.0.6",
Expand All @@ -34,6 +34,7 @@
"redux": "^4.0.1",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.3.0",
"reselect": "^4.0.0",
"source-map-explorer": "^1.7.0",
"typeface-roboto": "^0.0.54",
"whatwg-fetch": "^3.0.0"
Expand Down
13 changes: 13 additions & 0 deletions src/actions/activeModalDimension.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {
SET_ACTIVE_MODAL_DIMENSION,
CLEAR_ACTIVE_MODAL_DIMENSION,
} from '../reducers/activeModalDimension';

export const acSetActiveModalDimension = value => ({
type: SET_ACTIVE_MODAL_DIMENSION,
value,
});

export const acClearActiveModalDimension = () => ({
type: CLEAR_ACTIVE_MODAL_DIMENSION,
});
29 changes: 29 additions & 0 deletions src/actions/dimensions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import keyBy from 'lodash/keyBy';
import sortBy from 'lodash/sortBy';
import { SET_DIMENSIONS } from '../reducers/dimensions';
import { apiFetchDimensions } from '@dhis2/d2-ui-analytics';
import { sGetSettingsDisplayNameProperty } from '../reducers/settings';

export const acSetDimensions = dimensions => ({
type: SET_DIMENSIONS,
value: keyBy(sortBy(dimensions, [d => d.name.toLowerCase()]), 'id'),
});

export const tSetDimensions = d2 => async (dispatch, getState) => {
const onSuccess = dimensions => {
dispatch(acSetDimensions(dimensions));
};

const onError = error => {
console.log('Error (apiFetchDimensions): ', error);
return error;
};

try {
const displayNameProp = sGetSettingsDisplayNameProperty(getState());
const dimensions = await apiFetchDimensions(d2, displayNameProp);
return onSuccess(dimensions);
} catch (err) {
return onError(err);
}
};
16 changes: 16 additions & 0 deletions src/actions/editItemFilters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {
REMOVE_EDIT_ITEM_FILTER,
SET_EDIT_ITEM_FILTERS,
} from '../reducers/editItemFilters';

// actions

export const acRemoveEditItemFilter = id => ({
type: REMOVE_EDIT_ITEM_FILTER,
id,
});

export const acSetEditItemFilters = filters => ({
type: SET_EDIT_ITEM_FILTERS,
filters,
});
11 changes: 0 additions & 11 deletions src/actions/itemFilter.js

This file was deleted.

24 changes: 24 additions & 0 deletions src/actions/itemFilters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {
ADD_ITEM_FILTER,
REMOVE_ITEM_FILTER,
SET_ITEM_FILTERS,
} from '../reducers/itemFilters';

export const FILTER_ORG_UNIT = 'ou';

// actions

export const acAddItemFilter = filter => ({
type: ADD_ITEM_FILTER,
filter,
});

export const acRemoveItemFilter = id => ({
type: REMOVE_ITEM_FILTER,
id,
});

export const acSetItemFilters = filters => ({
type: SET_ITEM_FILTERS,
filters,
});
7 changes: 1 addition & 6 deletions src/actions/selected.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,7 @@ export const tSetSelectedDashboardById = id => async (dispatch, getState) => {
case MAP:
case EVENT_REPORT:
case EVENT_CHART:
dispatch(
acReceivedVisualization(
extractFavorite(item),
item.type
)
);
dispatch(acReceivedVisualization(extractFavorite(item)));
break;
case MESSAGES:
dispatch(tGetMessages(id));
Expand Down
9 changes: 5 additions & 4 deletions src/api/metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,12 @@ export const getMapFields = () => [
// Get more info about the favorite of a dashboard item
export const apiFetchFavorite = (id, type, { fields }) =>
getInstance().then(d2 =>
d2.Api.getApi().get(
`${getEndPointName(type)}/${id}?fields=${fields ||
d2.Api.getApi().get(`${getEndPointName(type)}/${id}`, {
fields:
fields ||
getFavoriteFields({
withDimensions: true,
withOptions: true,
})}`
)
}),
})
);
18 changes: 0 additions & 18 deletions src/api/orgUnits.js

This file was deleted.

3 changes: 3 additions & 0 deletions src/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { EDIT, VIEW, NEW } from './Dashboard/dashboardModes';
import { acReceivedUser } from '../actions/user';
import { tFetchDashboards } from '../actions/dashboards';
import { tSetControlBarRows } from '../actions/controlBar';
import { tSetDimensions } from '../actions/dimensions';
import Dashboard from './Dashboard/Dashboard';
import SnackbarMessage from './SnackbarMessage/SnackbarMessage';

Expand All @@ -20,6 +21,7 @@ export class App extends Component {
this.props.setCurrentUser(this.props.d2.currentUser);
this.props.fetchDashboards();
this.props.setControlBarRows();
this.props.setDimensions(this.props.d2);
}

getChildContext() {
Expand Down Expand Up @@ -81,6 +83,7 @@ const mapDispatchToProps = dispatch => {
setCurrentUser: currentUser => dispatch(acReceivedUser(currentUser)),
fetchDashboards: () => dispatch(tFetchDashboards()),
setControlBarRows: () => dispatch(tSetControlBarRows()),
setDimensions: d2 => dispatch(tSetDimensions(d2)),
};
};

Expand Down
2 changes: 2 additions & 0 deletions src/components/Dashboard/DashboardContent.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React, { Fragment } from 'react';
import TitleBar from '../TitleBar/TitleBar';
import ItemGrid from '../ItemGrid/ItemGrid';
import FilterBar from '../FilterBar/FilterBar';

export const DashboardContent = props => (
<Fragment>
<TitleBar edit={props.editMode} />
<FilterBar />
<ItemGrid edit={props.editMode} />
</Fragment>
);
Expand Down
71 changes: 71 additions & 0 deletions src/components/FilterBar/FilterBadge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';

import i18n from '@dhis2/d2-i18n';

import { colors } from '../../modules/colors';

const styles = {
badgeContainer: {
margin: '0 4px',
padding: '0 16px',
borderRadius: '4px',
color: colors.white,
backgroundColor: '#212934',
height: 36,
display: 'flex',
alignItems: 'center',
},
badge: {
fontSize: '13px',
cursor: 'pointer',
},
badgeRemove: {
fontSize: '12px',
textDecoration: 'underline',
marginLeft: '24px',
cursor: 'pointer',
},
};

class FilterBadge extends Component {
onClick = id => () => this.props.onClick(id);
onRemove = id => () => this.props.onRemove(id);

render() {
const { data } = this.props;

return (
<div style={styles.badgeContainer}>
<span style={styles.badge} onClick={this.onClick(data.id)}>
{`${data.name}: ${
data.values.length > 1
? i18n.t('{{count}} selected', {
count: data.values.length,
})
: data.values[0].name
}`}
</span>
<span
style={styles.badgeRemove}
onClick={this.onRemove(data.id)}
>
{i18n.t('Remove')}
</span>
</div>
);
}
}

FilterBadge.propTypes = {
data: PropTypes.object.isRequired,
onClick: PropTypes.func.isRequired,
onRemove: PropTypes.func.isRequired,
};

FilterBadge.defaultProps = {
onClick: Function.prototype,
onRemove: Function.prototype,
};

export default FilterBadge;
Loading

0 comments on commit bc22817

Please sign in to comment.