Concept note for dashboard

Proposal: Build a dashboard framework to enable implementers to configure dashboards dynamically.

Context: We are starting an engagement with a client who is already using OpenMRS. One of their requirement is to be able to see a dashboard for each role. Based on the role of the user logged-in, there will be different widgets on the dashboard. As of now, the requirement is to have 3 types of widgets on the dashboard:

  • ToDo Dashboard Widget: Show the Tasks need to be performed by the user.
  • Appointments Widget: To be able to view a list of appointments for today.
  • Charting and Reporting Widgets: To be able to view Charts and Reports for specific KPIs.

We feel this might be a good opportunity to engage with the community and build a Generic Dashboard Engine which can be used to configure different type of dashboards(Patient Dashboard, User Dashboard, Role Based Dashboard) according to the need. The engine will be able to dynamically load different widgets based on the configuration. This will provide the flexibility to add a widget based on need with less/no code changes.

We have attached a concept note for the same and would like to hear the thoughts from community about it. Please feel free to share your opinions.Concept Note_ Dashboard Framework for OpenMRS.pdf (116.9 KB)

cc: @angshuonline @joeldenning @jdick @mksd @rrameshbtech

1 Like

Sample Dashboard Definition. This will be evolving over the period of time.

{
  "title": "Sample Dashboard",
  "sections": [
    {
      "title": "Sample Widget",
      "displayOrder": 1,
      "widgetPath": "path/to/sample/widget's/library",
      "properties": {
        "propertyKey": "propertyValue"
      },
      "externalLibraries": {
        "libraryName": "path/to/external/library"
      }
    },
    {
      "title": "My Todos",
      "displayOrder": 2,
      "widgetPath": "path/to/todo/widget's/library",
      "properties": {
        "api": "api/path/to/get/todos",
        "durationInHours": 24,
        "hideDoneTodos": true
      },
      "externalLibraries": {
        "react-table": "path/to/react/table"
      }
    }
  ]
}
1 Like

Thanks. I am sure things will evolve, but few early queries:

  • I am assuming that sections are for grouping controls/widgets together? Would they have a title?
  • How would you determine, which control is displayed where within a section (e.g. column)?
  • Would the dashboard view be responsive?

Hi Angshu,

Please find the response below each question.

  • I am assuming that sections are for grouping controls/widgets together? Would they have a title?

For now, sections will contain list of widgets that should be shown in the dashboard. We do not have internal grouping for now.

  • How would you determine, which control is displayed where within a section (e.g. column)?

We want to start with simple. So we will use displayOrder to decide where to show the section (This will help us to achieve responsiveness easily). I guess we could change once after the designs been frozen.

  • Would the dashboard view be responsive?

Yes. Dashboard will be responsive.

In regards to the widgetPath:

Within microfrontends, there are “modules” and “exports for those modules,” which are what I’d recommend using for loading the correct module:

{
  "sections": [
    { "moduleName": "@bahmni/esm-dashboard", "moduleExport": "nameOfExport" }
  ]
}
// inside of @bahmni/esm-dashboard repo
export const nameOfExport = OurReactComp

One question for you – what are “externalLibraries” in this context?

      "externalLibraries": {

@sukreet I see most of these ideas are brand new - any chance of leveraging the ideas from the existing clinician facing dashboard, dashboard widgets and app configuration such as

https://wiki.openmrs.org/display/docs/Patient+Summary+Widget+Documentation

That way you are not building from scratch and not re-inventing the wheel with things like configurations, privileges, age of observations to show, limits, etc

@joeldenning are the micro-front ends going to do away with the current OpenMRS referencing model of module-provider:fragment which is a relative path without having to specify absolute paths as in this example

1 Like

Yes, fragments and module extensions are for server rendered java pages, not for client rendered JavaScript. The microfrontends code is all client rendered JavaScript (SPA). The older, existing ways of doing module extensions will continue to work for those using jsp.

@joeldenning , can there be a way, that the person defining the dashboard, does not really need to know where the widgets are loaded from? and technicalities of the widgets? If there was a visual editor, probably I would have a different view, but considering that these would be hand-written, can the definition be simpler.

What would be great if we can define in the dashboard json - what widget type are displayed, where and with what configuration/properties. Based on the type, if the framework can identify what widget to load (and from where), and pass on the defined properties to the widget and the widget can render whatever way it likes.

Now this would mean that the esm modules (specified in import-map) would somehow have to register themselves to a “directory” or sorts or for the framework to query modules to identify whether they are of certain type.

Thoughts?

can there be a way, that the person defining the dashboard, does not really need to know where the widgets are loaded from?

Bahmni could put all the widgets into the same repo, and then the code wouldn’t need to be told which module they are in. This would probably work well for you.

For the main OpenMRS patient chart, we’re likely going to require the moduleName, since not all the main patient chart widgets across all distributions will be in the same git repo / in-browser module. However, that’s a separate project from the Bahmni project so they don’t have to solve the problem in the same way.

I was not referring to Bahmni as such. (even in Bahmni, we have different repos). The dashboard in question - is not for Bahmni!

but in Bahmni, we have a form engine - (Forms 2.0) - where widgets can be loaded from different bundles. What we expect is that widgets register with a “directory” for what type/sorts they can render. Same - for design time (we have WYSIWYG editor) - widgets describe themselves and register their designers controls.

Once done, when the definition is loaded, the form renderer just goes through the JSON definition - looks up the widgets and invokes the widget with the properties from the definition.

Bahmni’s dashboard works the same way - although for external widgets someone needs to tell where to fine the source code - but thats one time definition in a file - not for each dashboard widget.

What I am saying is that shouldn’t the new “client side way” learn and build upon the ways of the old JSP/GSP framework, leveraging the same conventions - that way the learning curve and 13 years of knowledge are not thrown away

Thanks @ssmusoke for your response. The reason for most of the naming convention changes are because of some or not relevant for the current implementation (like id, description) and some are more technical like instananceOf. So we wanted the naming convention to be more readable for none/less technical person too. We can change some of the key names like properties to config if we are good with that. Please provide your thoughts on this.

After discussion with @angshuonline & adopting @joeldenning reply. below is the modified config.

{
  "title": "Sample Dashboard",
  "sections": [
    {
      "title": "Sample Widget",
      "displayOrder": 1,
      "library": {
        "module": "@bahmni/esm-sample-control",
        "name": "sampleControl"
      },
      "properties": {
        "propertyKey": "propertyValue"
      }
    },
    {
      "title": "My Todos",
      "displayOrder": 1,
      "library": {
        "module": "@bahmni/esm-todo",
        "name": "todoControl"
      },
      "properties": {
        "api": "api/path/to/get/todos",
        "durationInHours": 24,
        "hideDoneTodos": true
      }
    }
  ]
}

I will just use an example from our implementation for a dashboard widget

{
"id": "ugandaemr.dashboardwidget.EIDSummary",
"instanceOf": "coreapps.template.dashboardWidget",
"description": "Summary for an HIV exposed infant",
"order": 10,
"config": {
  "widget": "latestobsforconceptlist",
  "icon": "icon-user-md",
  "label": "EID SUMMARY",
  "maxAge": "120m",
  "concepts": "51941f01-307f-44ca-9351-401bc008a208,b6a6210b-ccdf-45fc-80dd-1567f65e2d98,1f627527-2f97-4f21-9b61-2b79d887950f,e1b4efbf-0dff-4e9e-a2f2-34edcb02a5d0,4092ad52-3db3-47f5-b497-126e1202f1eb,162881AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,977b16f4-2d3e-40d2-ba51-54794c98f7ef"
},
"extensions": [
  {
    "id": "org.openmrs.module.coreapps.mostRecentVitals.clinicianDashboardSecondColumn",
    "appId": "ugandaemr.dashboardwidget.EIDSummary",
    "extensionPointId": "patientDashboard.secondColumnFragments",
    "extensionParams": {
      "provider": "coreapps",
      "fragment": "dashboardwidgets/dashboardWidget"
    },
    "require":"patient.person.age < 10"
  }
]

}

Thoughts and potential ideas

  • id - as there may be multiple instances of the same widget with different configurations, also use it to replace extensions.id and extensions.appId
  • instanceOf - type which can also replace the config.widget variable too
  • description is necessary so that a user can understand what the contents are
  • title/label: move into this level as this is what is displayed
  • order: keep for sorting and ordering (the higher the number the lower the widget is placed)
  • icon: move to top level too
  • extensions: this is where the widget is placed
    • extensionPointId - this is a very powerful feature as it provides where the widget will be placed, I recommend you leverage this approach
    • extensionParams - rename to source - of the widget, you can use module instead of provider, and widget name instead of fragment so that you do not have to use the full path (this requires a registry in place)
    • require: important for filtering to where it is displayed and shown

attn: @bistenes - this maybe your first case of config :slight_smile:

  • Id - not needed. Even if within the same dashboard config, the same widget is used - the configs would be different. example: a widget chart working on two different dataset.
  • instanceof - no inference by type. so not applicable I would think.
  • order - I think “displayOrder” would be more appropriate if the team uses an “order” field for placement. IMHO, its too simplistic - but I will let the team decide depending on how they are solutioning placement of widgets.

regarding “config”, it would be always specific to the widget - so I would let the implementer decide. As long as they follow the process of documenting inline of what Brandon is suggesting in MF Squad. Regarding “extensions” - I think its better to let things evolve, rather than fix up definition upfront. I do think extensions are really powerful - but I think we need more discussions.

@rrameshbtech You will need to have multiple levels in the config

  • dashboard: the overall container
  • sections: groupings of containers?
  • widgets: actual widgets which are placed in the containers

Questions:

  • how to define sections will these be rows or columns or a combination of both
  • Will it be a fluid or fixed layout?

@ssmusoke

Thank for the suggestions.

As of now we have 2 levels of config.

  • Dashboard
  • Sections (contains list of widgets a dashboard has)

As @angshuonline has mentioned, we want to evolve the design based on the need. It would help us to get simpler config.

  • how to define sections will these be rows or columns or a combination of both

For now, we want to start with just display order & evolve based on the need. May be in evolving it may get row, column.

  • Will it be a fluid or fixed layout?

It will be a fluid layout.