From 3d454d043b5f06e142699f0ec281ad24647c7aa2 Mon Sep 17 00:00:00 2001 From: kchelstowski Date: Thu, 21 Jul 2022 15:54:03 +0200 Subject: [PATCH 1/2] OBPIH-4277 Add option to make separate dashboard pages --- grails-app/conf/Config.groovy | 393 +++++++++--------- grails-app/conf/UrlMappings.groovy | 12 +- .../api/DashboardApiController.groovy | 7 +- .../org/pih/warehouse/core/UserService.groovy | 20 +- package.json | 2 +- src/js/actions/index.js | 8 +- src/js/components/dashboard/Dashboard.jsx | 46 +- 7 files changed, 269 insertions(+), 219 deletions(-) diff --git a/grails-app/conf/Config.groovy b/grails-app/conf/Config.groovy index 5e05a9b9328..712e5aae03a 100644 --- a/grails-app/conf/Config.groovy +++ b/grails-app/conf/Config.groovy @@ -409,206 +409,211 @@ openboxes { openboxes { dashboardConfig { dashboards { - personal { - name = "My Dashboard" - filters {} - widgets = [ - [ - widgetId: "inventoryByLotAndBin", - order: 1 - ], - [ - widgetId: "receivingBin", - order: 2 - ], - [ - widgetId: "inProgressShipments", - order: 3 - ], - [ - widgetId: "inProgressPutaways", - order: 4 - ], - - [ - widgetId: "inventorySummary", - order: 1 - ], - [ - widgetId: "expirationSummary", - order: 2 - ], - [ - widgetId: "incomingStock", - order: 3 - ], - [ - widgetId: "outgoingStock", - order: 4 - ], - [ - widgetId: "delayedShipments", - order: 5 - ], - [ - widgetId: "discrepancy", - order: 6 - ] - ] - } - warehouse { - name = "Warehouse Management" - filters {} - widgets = [ - [ - widgetId: "inventoryByLotAndBin", - order: 1 - ], - [ - widgetId: "receivingBin", - order: 2 - ], - [ - widgetId: "inProgressShipments", - order: 3 - ], - [ - widgetId: "inProgressPutaways", - order: 4 - ], - [ - widgetId: "itemsInventoried", - order: 5 - ], - - [ - widgetId: "inventorySummary", - order: 1 - ], - [ - widgetId: "expirationSummary", - order: 2 - ], - [ - widgetId: "incomingStock", - order: 3 - ], - [ - widgetId: "outgoingStock", - order: 4 - ], - [ - widgetId: "delayedShipments", - order: 5 - ], - [ - widgetId: "discrepancy", - order: 6 - ] - ] - } - inventory { - name = "Inventory Management" - filters {} - widgets = [ - [ - widgetId: "receivingBin", - order: 1 - ], - [ - widgetId: "defaultBin", - order: 2 - ], - [ - widgetId: "negativeInventory", - order: 3 - ], - [ - widgetId: "expiredStock", - order: 4 - ], - [ - widgetId: "openStockRequests", - order: 5 - ], - - [ - widgetId: "delayedShipments", - order: 1 - ], - [ - widgetId: "productsInventoried", - order: 2 - ] - ] - } - transaction { - name = "Transaction Management" - filters {} - widgets = [ - [ - widgetId: "fillRateSnapshot", - order: 1 - ], - - [ - widgetId: "receivedStockMovements", - order: 1 - ], - [ - widgetId: "sentStockMovements", - order: 2 - ], - [ - widgetId: "lossCausedByExpiry", - order: 3 - ], - [ - widgetId: "percentageAdHoc", - order: 4 - ], - [ - widgetId: "fillRate", - order: 5 - ], - [ - widgetId: "stockOutLastMonth", - order: 6 - ] - ] - } - fillRate { - name = "Fill Rate" - filters { - category { - endpoint = "/${appName}/categoryApi/list" + mainDashboard { + personal { + name = "My Dashboard" + filters {} + widgets = [ + [ + widgetId: "inventoryByLotAndBin", + order : 1 + ], + [ + widgetId: "receivingBin", + order : 2 + ], + [ + widgetId: "inProgressShipments", + order : 3 + ], + [ + widgetId: "inProgressPutaways", + order : 4 + ], + + [ + widgetId: "inventorySummary", + order : 1 + ], + [ + widgetId: "expirationSummary", + order : 2 + ], + [ + widgetId: "incomingStock", + order : 3 + ], + [ + widgetId: "outgoingStock", + order : 4 + ], + [ + widgetId: "delayedShipments", + order : 5 + ], + [ + widgetId: "discrepancy", + order : 6 + ] + ] + } + warehouse { + name = "Warehouse Management" + filters {} + widgets = [ + [ + widgetId: "inventoryByLotAndBin", + order : 1 + ], + [ + widgetId: "receivingBin", + order : 2 + ], + [ + widgetId: "inProgressShipments", + order : 3 + ], + [ + widgetId: "inProgressPutaways", + order : 4 + ], + [ + widgetId: "itemsInventoried", + order : 5 + ], + + [ + widgetId: "inventorySummary", + order : 1 + ], + [ + widgetId: "expirationSummary", + order : 2 + ], + [ + widgetId: "incomingStock", + order : 3 + ], + [ + widgetId: "outgoingStock", + order : 4 + ], + [ + widgetId: "delayedShipments", + order : 5 + ], + [ + widgetId: "discrepancy", + order : 6 + ] + ] + } + inventory { + name = "Inventory Management" + filters {} + widgets = [ + [ + widgetId: "receivingBin", + order : 1 + ], + [ + widgetId: "defaultBin", + order : 2 + ], + [ + widgetId: "negativeInventory", + order : 3 + ], + [ + widgetId: "expiredStock", + order : 4 + ], + [ + widgetId: "openStockRequests", + order : 5 + ], + + [ + widgetId: "delayedShipments", + order : 1 + ], + [ + widgetId: "productsInventoried", + order : 2 + ] + ] + } + transaction { + name = "Transaction Management" + filters {} + widgets = [ + [ + widgetId: "fillRateSnapshot", + order : 1 + ], + + [ + widgetId: "receivedStockMovements", + order : 1 + ], + [ + widgetId: "sentStockMovements", + order : 2 + ], + [ + widgetId: "lossCausedByExpiry", + order : 3 + ], + [ + widgetId: "percentageAdHoc", + order : 4 + ], + [ + widgetId: "fillRate", + order : 5 + ], + [ + widgetId: "stockOutLastMonth", + order : 6 + ] + ] + } + fillRate { + name = "Fill Rate" + filters { + category { + endpoint = "/${appName}/categoryApi/list" + } } + widgets = [ + [ + widgetId: "fillRateSnapshot", + order : 1 + ], + + [ + widgetId: "fillRate", + order : 1 + ] + ] } - widgets = [ - [ - widgetId: "fillRateSnapshot", - order: 1 - ], - - [ - widgetId: "fillRate", - order: 1 - ] - ] + } supplier { - name = "Supplier Dashboard" - filters { - supplier { - endpoint = "/${appName}/api/locations?direction=INBOUND" + supplier { + name = "Supplier Dashboard" + filters { + supplier { + endpoint = "/${appName}/api/locations?direction=INBOUND" + } } - } - widgets = [ - [ - widgetId: "numberOfOpenPurchaseOrders", - order: 1 + widgets = [ + [ + widgetId: "numberOfOpenPurchaseOrders", + order : 1 + ] ] - ] + } } } // TODO: OBPIH-4384 Refactor indicator filters to be more generic (currently filters are hardcoded on the frontend, these should be defined here and rendered there basing on config) diff --git a/grails-app/conf/UrlMappings.groovy b/grails-app/conf/UrlMappings.groovy index 6b75e20405c..05d52289eba 100644 --- a/grails-app/conf/UrlMappings.groovy +++ b/grails-app/conf/UrlMappings.groovy @@ -432,7 +432,17 @@ class UrlMappings { "/api/dashboard/config"(parseRequest: true) { controller = { "dashboardApi" } - action = [GET: "config", POST: "updateConfig"] + action = [POST: "updateConfig"] + } + + "/api/dashboard/$id/config"(parseRequest: true) { + controller = { "dashboardApi" } + action = [GET: "config"] + } + + "/api/dashboard/$id/subdashboardKeys"(parseRequest: true) { + controller = { "dashboardApi" } + action = [GET: "getSubdashboardKeys"] } "/api/dashboard/breadcrumbsConfig"(parseRequest: true) { diff --git a/grails-app/controllers/org/pih/warehouse/api/DashboardApiController.groovy b/grails-app/controllers/org/pih/warehouse/api/DashboardApiController.groovy index 59019a477f4..352ced66164 100644 --- a/grails-app/controllers/org/pih/warehouse/api/DashboardApiController.groovy +++ b/grails-app/controllers/org/pih/warehouse/api/DashboardApiController.groovy @@ -17,11 +17,16 @@ class DashboardApiController { def config = { User user = User.get(session.user.id) - def config = userService.getDashboardConfig(user) + def config = userService.getDashboardConfig(user, params.id) render(config as JSON) } + def getSubdashboardKeys = { + def config = grailsApplication.config.openboxes.dashboardConfig.dashboards[params.id ?: "mainDashboard"] + render(config.keySet() as JSON) + } + def updateConfig = { User user = User.get(session.user.id) def config = userService.updateDashboardConfig(user, request.JSON) diff --git a/grails-app/services/org/pih/warehouse/core/UserService.groovy b/grails-app/services/org/pih/warehouse/core/UserService.groovy index 29d5527c89f..002c5236bf8 100644 --- a/grails-app/services/org/pih/warehouse/core/UserService.groovy +++ b/grails-app/services/org/pih/warehouse/core/UserService.groovy @@ -402,27 +402,31 @@ class UserService { return false } - def getDashboardConfig(User user) { - def config = grailsApplication.config.openboxes.dashboardConfig + def getDashboardConfig(User user, String id) { + def fullConfig = grailsApplication.config.openboxes.dashboardConfig + def resultConfig = [ + dashboards: fullConfig.dashboards[id ?: "mainDashboard"], + dashboardWidgets: fullConfig.dashboardWidgets + ] def userConfig = user.deserializeDashboardConfig() Boolean configChanged = false if (userConfig != null) { int userConfigSize = userConfig.size() - int configSize = config.dashboards.size() + int resultConfigSize = resultConfig.dashboards.size() // If the size is different, that mean that the config has changed - if (userConfigSize != configSize) { - return config + if (userConfigSize != resultConfigSize) { + return resultConfig } if (!configChanged) { - config = [ + resultConfig = [ dashboards : userConfig, - dashboardWidgets : config.dashboardWidgets + dashboardWidgets : resultConfig.dashboardWidgets ] } } - return config + return resultConfig } def updateDashboardConfig(User user, Object config) { diff --git a/package.json b/package.json index c22681946cb..1a1a48ea2f5 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "test": "test" }, "scripts": { - "watch": "webpack --watch --colors --progress --mode=development", + "watch": "webpack --watch --progress --mode=development", "bundle": "webpack --no-color --mode=production", "test": "jest", "styleguide": "styleguidist server", diff --git a/src/js/actions/index.js b/src/js/actions/index.js index c24c774a478..0235457137b 100644 --- a/src/js/actions/index.js +++ b/src/js/actions/index.js @@ -377,9 +377,9 @@ export function reorderIndicators({ oldIndex, newIndex }, e, type) { }; } -export function fetchConfigAndData(locationId, config = 'personal', userId, filterSelected) { +export function fetchConfigAndData(locationId, config = 'personal', userId, id, filterSelected) { return (dispatch) => { - apiClient.get('/openboxes/api/dashboard/config').then((res) => { + apiClient.get(`/openboxes/api/dashboard/${id}/config`).then((res) => { dispatch({ type: FETCH_CONFIG_AND_SET_ACTIVE, payload: { @@ -392,9 +392,9 @@ export function fetchConfigAndData(locationId, config = 'personal', userId, filt }; } -export function fetchConfig() { +export function fetchConfig(id) { return (dispatch) => { - apiClient.get('/openboxes/api/dashboard/config').then((res) => { + apiClient.get(`/openboxes/api/dashboard/${id}/config`).then((res) => { dispatch({ type: FETCH_CONFIG, payload: { diff --git a/src/js/components/dashboard/Dashboard.jsx b/src/js/components/dashboard/Dashboard.jsx index 1a10965eb18..8f0e4491b31 100644 --- a/src/js/components/dashboard/Dashboard.jsx +++ b/src/js/components/dashboard/Dashboard.jsx @@ -138,6 +138,8 @@ const ConfigurationsList = ({ ); }; +const MAIN_DASHBOARD_CONFIG = 'mainDashboard'; + class Dashboard extends Component { constructor(props) { @@ -151,37 +153,59 @@ class Dashboard extends Component { configModified: false, allLocations: [], pageFilters: [], + subdashboardKeys: [], }; - this.config = 'personal'; this.fetchLocations(); } componentDidMount() { - this.config = this.getConfigIdFromParams() || sessionStorage.getItem('dashboardKey') || this.config; if (this.props.currentLocation !== '') { - this.fetchData(this.config); + this.getSubdashboardKeys().then(() => this.fetchData(this.determineActiveConfig())); } this.loadPageFilters(this.props.activeConfig); } componentDidUpdate(prevProps) { - this.config = this.getConfigIdFromParams() || sessionStorage.getItem('dashboardKey') || this.config; const prevLocation = prevProps.currentLocation; const newLocation = this.props.currentLocation; if (prevLocation !== newLocation) { - this.fetchData(this.config); + this.getSubdashboardKeys().then(() => this.fetchData(this.determineActiveConfig())); } if (prevProps.dashboardConfig.dashboards !== this.props.dashboardConfig.dashboards) { this.loadPageFilters(this.props.activeConfig); } } - getConfigIdFromParams() { - if (this.props.match.params.configId) { - return this.props.match.params.configId === 'index' ? this.props.activeConfig : this.props.match.params.configId; + getDashboardIdFromParams() { + const dashboardId = this.props.match.params.configId; + if (dashboardId && dashboardId !== 'index') { + return dashboardId; } + return MAIN_DASHBOARD_CONFIG; + } - return ''; + getSubdashboardKeys() { + const dashboardId = this.getDashboardIdFromParams(); + const url = `/openboxes/api/dashboard/${dashboardId}/subdashboardKeys`; + return apiClient.get(url) + .then((res) => { + const subdashboardKeys = res.data; + if (subdashboardKeys) { + this.setState({ subdashboardKeys }); + } + }); + } + + determineActiveConfig() { + if (this.props.match.params.configId && this.props.match.params.configId !== 'index') { + return this.props.match.params.configId; + } + const configFromSessionStorage = sessionStorage.getItem('dashboardKey'); + // eslint-disable-next-line max-len + if (configFromSessionStorage && this.state.subdashboardKeys.includes(configFromSessionStorage)) { + return configFromSessionStorage; + } + return 'personal'; } dataFetched = false; @@ -236,6 +260,8 @@ class Dashboard extends Component { this.props.currentLocation, config, this.props.currentUser, + // Determine which dashboard (id) to fetch + this.getDashboardIdFromParams(), ); } }; @@ -261,7 +287,7 @@ class Dashboard extends Component { }; apiClient.post(url, payload).then(() => { - this.props.fetchConfig(); + this.props.fetchConfig(this.getDashboardIdFromParams()); this.setState({ configModified: false }); }); }; From c8ba325793987614e7e181be04aed0fb954b2e4d Mon Sep 17 00:00:00 2001 From: kchelstowski Date: Fri, 22 Jul 2022 11:07:54 +0200 Subject: [PATCH 2/2] OBPIH-4277 Fixes after review --- grails-app/conf/Config.groovy | 1 + .../api/DashboardApiController.groovy | 3 ++- .../org/pih/warehouse/core/UserService.groovy | 24 +++++++++---------- src/js/actions/index.js | 2 +- src/js/components/dashboard/Dashboard.jsx | 20 ++++++++-------- .../dashboard/UnarchiveIndicators.jsx | 2 +- 6 files changed, 26 insertions(+), 26 deletions(-) diff --git a/grails-app/conf/Config.groovy b/grails-app/conf/Config.groovy index 712e5aae03a..2f7cc2d8afa 100644 --- a/grails-app/conf/Config.groovy +++ b/grails-app/conf/Config.groovy @@ -408,6 +408,7 @@ openboxes { openboxes { dashboardConfig { + mainDashboardId = "mainDashboard" dashboards { mainDashboard { personal { diff --git a/grails-app/controllers/org/pih/warehouse/api/DashboardApiController.groovy b/grails-app/controllers/org/pih/warehouse/api/DashboardApiController.groovy index 352ced66164..a3900f2867c 100644 --- a/grails-app/controllers/org/pih/warehouse/api/DashboardApiController.groovy +++ b/grails-app/controllers/org/pih/warehouse/api/DashboardApiController.groovy @@ -23,7 +23,8 @@ class DashboardApiController { } def getSubdashboardKeys = { - def config = grailsApplication.config.openboxes.dashboardConfig.dashboards[params.id ?: "mainDashboard"] + def mainDashboardId = grailsApplication.config.openboxes.dashboardConfig.mainDashboardId + def config = grailsApplication.config.openboxes.dashboardConfig.dashboards[params.id ?: mainDashboardId] render(config.keySet() as JSON) } diff --git a/grails-app/services/org/pih/warehouse/core/UserService.groovy b/grails-app/services/org/pih/warehouse/core/UserService.groovy index 002c5236bf8..44cfe150310 100644 --- a/grails-app/services/org/pih/warehouse/core/UserService.groovy +++ b/grails-app/services/org/pih/warehouse/core/UserService.groovy @@ -404,25 +404,23 @@ class UserService { def getDashboardConfig(User user, String id) { def fullConfig = grailsApplication.config.openboxes.dashboardConfig + def mainDashboardId = grailsApplication.config.openboxes.dashboardConfig.mainDashboardId def resultConfig = [ - dashboards: fullConfig.dashboards[id ?: "mainDashboard"], + dashboard: fullConfig.dashboards[id ?: mainDashboardId], dashboardWidgets: fullConfig.dashboardWidgets ] def userConfig = user.deserializeDashboardConfig() - Boolean configChanged = false if (userConfig != null) { - int userConfigSize = userConfig.size() - int resultConfigSize = resultConfig.dashboards.size() - // If the size is different, that mean that the config has changed - if (userConfigSize != resultConfigSize) { - return resultConfig - } - if (!configChanged) { - resultConfig = [ - dashboards : userConfig, - dashboardWidgets : resultConfig.dashboardWidgets - ] + if (id == mainDashboardId) { + int userConfigSize = userConfig.size() + int resultConfigSize = resultConfig.dashboard.size() + if (userConfigSize == resultConfigSize) { + resultConfig = [ + dashboard : userConfig, + dashboardWidgets : resultConfig.dashboardWidgets + ] + } } } diff --git a/src/js/actions/index.js b/src/js/actions/index.js index 0235457137b..ca4ce5340d5 100644 --- a/src/js/actions/index.js +++ b/src/js/actions/index.js @@ -310,7 +310,7 @@ export function reloadIndicator(indicatorConfig, params, locationId) { function getData(dispatch, dashboardConfig, locationId, config = 'personal', userId = '') { // new reference so that the original config is not modified - const dashboard = dashboardConfig.dashboards[config] || {}; + const dashboard = dashboardConfig.dashboard[config] || {}; const widgets = _.map(dashboard.widgets, widget => ({ ...dashboardConfig.dashboardWidgets[widget.widgetId], order: widget.order, diff --git a/src/js/components/dashboard/Dashboard.jsx b/src/js/components/dashboard/Dashboard.jsx index 8f0e4491b31..8bb5ea7e640 100644 --- a/src/js/components/dashboard/Dashboard.jsx +++ b/src/js/components/dashboard/Dashboard.jsx @@ -171,7 +171,7 @@ class Dashboard extends Component { if (prevLocation !== newLocation) { this.getSubdashboardKeys().then(() => this.fetchData(this.determineActiveConfig())); } - if (prevProps.dashboardConfig.dashboards !== this.props.dashboardConfig.dashboards) { + if (prevProps.dashboardConfig.dashboard !== this.props.dashboardConfig.dashboard) { this.loadPageFilters(this.props.activeConfig); } } @@ -212,8 +212,8 @@ class Dashboard extends Component { loadPageFilters(config = '') { let pageFilters = []; - if (this.props.dashboardConfig.dashboards) { - const allPages = Object.entries(this.props.dashboardConfig.dashboards) + if (this.props.dashboardConfig.dashboard) { + const allPages = Object.entries(this.props.dashboardConfig.dashboard) .map(([key, value]) => [key, value]); allPages.forEach((page) => { const filters = Object.entries(page[1].filters) @@ -247,7 +247,7 @@ class Dashboard extends Component { fetchData = (config = 'personal') => { sessionStorage.setItem('dashboardKey', config); this.props.resetIndicators(); - if (this.props.dashboardConfig && this.props.dashboardConfig.dashboards) { + if (this.props.dashboardConfig && this.props.dashboardConfig.dashboard) { this.props.fetchIndicators( this.props.dashboardConfig, config, @@ -279,9 +279,9 @@ class Dashboard extends Component { const url = '/openboxes/api/dashboard/config'; const payload = { - ...this.props.dashboardConfig.dashboards, + ...this.props.dashboardConfig.dashboard, [this.props.activeConfig]: { - ...this.props.dashboardConfig.dashboards[this.props.activeConfig], + ...this.props.dashboardConfig.dashboard[this.props.activeConfig], widgets, }, }; @@ -293,7 +293,7 @@ class Dashboard extends Component { }; loadIndicator = (widgetId, params) => { - const dashboardConf = this.props.dashboardConfig.dashboards[this.props.activeConfig]; + const dashboardConf = this.props.dashboardConfig.dashboard[this.props.activeConfig]; const widget = _.find(dashboardConf.widgets, w => w.widgetId === widgetId); const widgetConf = { ...this.props.dashboardConfig.dashboardWidgets[widgetId], @@ -392,7 +392,7 @@ class Dashboard extends Component { return (