Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PUI] Dashboard refactor #8278

Merged
merged 110 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
110 commits
Select commit Hold shift + click to select a range
40c21c5
Refactor plugin components into <RemoteComponent />
SchrodingersGat Oct 12, 2024
1f29019
Clean up footer
SchrodingersGat Oct 12, 2024
7cbb699
Allow BuildOrder list to be sorted by 'outstanding'
SchrodingersGat Oct 12, 2024
f12778b
Fix model name
SchrodingersGat Oct 12, 2024
c457697
Update BuildOrderTable filter
SchrodingersGat Oct 12, 2024
4adac19
Add StockItemTable column
SchrodingersGat Oct 12, 2024
55f11be
Working towards new dashboard
SchrodingersGat Oct 12, 2024
126bebe
Cleanup unused imports
SchrodingersGat Oct 12, 2024
103fdf8
Updates: Now rendering some custom widgets
SchrodingersGat Oct 12, 2024
f910dab
Define icons for model types
SchrodingersGat Oct 12, 2024
7e11789
Add icon
SchrodingersGat Oct 12, 2024
e77064c
Cleanup / refactor / delete
SchrodingersGat Oct 12, 2024
326eb07
Follow link for query count widgets
SchrodingersGat Oct 12, 2024
c863eab
Add some more widgets to the library
SchrodingersGat Oct 12, 2024
bc136e0
Remove old dashboard link in header
SchrodingersGat Oct 12, 2024
0706269
Remove feedback widget
SchrodingersGat Oct 12, 2024
2112b26
Merge remote-tracking branch 'origin/master' into dashboard-refactor
SchrodingersGat Oct 12, 2024
5694445
Bump API version
SchrodingersGat Oct 12, 2024
3c6e11d
Remove test widget
SchrodingersGat Oct 12, 2024
7e2d140
Rename "Home" -> "Dashboard"
SchrodingersGat Oct 12, 2024
d93c51a
Add some more widgets
SchrodingersGat Oct 12, 2024
4a16b56
Pass 'editable' property through to widgets
SchrodingersGat Oct 12, 2024
3230169
Cleanup
SchrodingersGat Oct 12, 2024
533f699
Add drawer for selecting new widgets
SchrodingersGat Oct 13, 2024
b2872d2
Allow different layouts per user on the same machine
SchrodingersGat Oct 13, 2024
f964beb
Fixes
SchrodingersGat Oct 13, 2024
af140bd
Add ability to *remove* widgets
SchrodingersGat Oct 13, 2024
e48f505
Add helpful button
SchrodingersGat Oct 13, 2024
ab19ef4
Add a keyboard shortcut
SchrodingersGat Oct 13, 2024
4237daa
Refactoring
SchrodingersGat Oct 13, 2024
ce44952
Add backend code for serving custom dashboard items
SchrodingersGat Oct 13, 2024
3da2799
Load dashboard items from plugins
SchrodingersGat Oct 13, 2024
b5eeb53
Tweak for dashboard item API query
SchrodingersGat Oct 13, 2024
a335800
Add message if no dashboard widgets are displayed
SchrodingersGat Oct 13, 2024
d92cf74
Refactoring main navigation menu
SchrodingersGat Oct 13, 2024
9b3216a
Remove playground
SchrodingersGat Oct 13, 2024
88d13c2
Add backend field for storing dashboard layout
SchrodingersGat Oct 13, 2024
148158f
Add extra type definitions for UseInstance
SchrodingersGat Oct 13, 2024
2ad1697
Merge remote-tracking branch 'origin/master' into dashboard-refactor
SchrodingersGat Oct 14, 2024
7b06cc0
Manual labels for builtin dashboard items
SchrodingersGat Oct 14, 2024
729105b
Shorten labels for more plugins
SchrodingersGat Oct 15, 2024
43f7d72
Adjust DashboardMenu
SchrodingersGat Oct 15, 2024
6b1583f
Reduce stored data
SchrodingersGat Oct 15, 2024
f900993
Add widget filter by text
SchrodingersGat Oct 15, 2024
1110dad
Merge remote-tracking branch 'origin/master' into dashboard-refactor
SchrodingersGat Oct 15, 2024
e3082de
Remove back-end settings
SchrodingersGat Oct 15, 2024
0fd7371
Update playwright tests for dashboard
SchrodingersGat Oct 15, 2024
c13c754
Updated tests
SchrodingersGat Oct 15, 2024
49878f7
Refactor backend API for fetching plugin features
SchrodingersGat Oct 15, 2024
0619fa5
Further fixes for back-end code
SchrodingersGat Oct 15, 2024
e5bb55a
More back-end fixes
SchrodingersGat Oct 15, 2024
afaf4a8
Refactor frontend:
SchrodingersGat Oct 15, 2024
fe5b51d
Further backend fixes
SchrodingersGat Oct 15, 2024
98d8c1d
Yet more backend fixes
SchrodingersGat Oct 15, 2024
397a1fa
Fix for custom plugin settings rendering
SchrodingersGat Oct 15, 2024
9b08dff
Enable plugin panels for part index and stock index pages
SchrodingersGat Oct 15, 2024
9fb58c0
Cleanup
SchrodingersGat Oct 15, 2024
a02f855
Merge remote-tracking branch 'origin/master' into dashboard-refactor
SchrodingersGat Oct 15, 2024
717e607
Fix nav menu
SchrodingersGat Oct 15, 2024
88c8223
Merge branch 'master' into dashboard-refactor
SchrodingersGat Oct 16, 2024
47182da
Merge branch 'master' into dashboard-refactor
SchrodingersGat Oct 17, 2024
fd8d7af
Merge remote-tracking branch 'origin/master' into dashboard-refactor
SchrodingersGat Oct 19, 2024
bb14cfa
Update typing
SchrodingersGat Oct 19, 2024
92bb647
Helper func to return all plugin settings as a dict
SchrodingersGat Oct 19, 2024
00eabad
Merge branch 'dashboard-refactor' of github.com:SchrodingersGat/Inven…
SchrodingersGat Oct 19, 2024
b841296
Merge branch 'master' into dashboard-refactor
SchrodingersGat Oct 20, 2024
24bbedd
Merge branch 'master' into dashboard-refactor
SchrodingersGat Oct 20, 2024
6668724
Merge branch 'dashboard-refactor' of github.com:SchrodingersGat/Inven…
SchrodingersGat Oct 23, 2024
6c3c18b
Merge remote-tracking branch 'origin/master' into dashboard-refactor
SchrodingersGat Oct 23, 2024
9e502d0
Update API version date
SchrodingersGat Oct 23, 2024
d7fe05c
Merge branch 'master' into dashboard-refactor
SchrodingersGat Oct 23, 2024
10adb64
Merge remote-tracking branch 'origin/master' into dashboard-refactor
SchrodingersGat Oct 24, 2024
156b408
Fix for UseInstancea
SchrodingersGat Oct 24, 2024
f30cf34
typing fix
SchrodingersGat Oct 24, 2024
087a519
Merge branch 'master' into dashboard-refactor
SchrodingersGat Oct 25, 2024
49ce9df
Tweak layout callbacks
SchrodingersGat Oct 25, 2024
cf182c9
Pass query parameters through to navigation functions
SchrodingersGat Oct 25, 2024
bf68968
Improve custom query display
SchrodingersGat Oct 25, 2024
1565522
Add "news" widget
SchrodingersGat Oct 26, 2024
4822931
Ensure links are prepended with base URL on receipt
SchrodingersGat Oct 26, 2024
27a19ea
Update NewsWidget
SchrodingersGat Oct 26, 2024
d3fd087
Bug fix
SchrodingersGat Oct 26, 2024
0ac7138
Refactor template editor tests
SchrodingersGat Oct 26, 2024
6c16fa1
Refactor unit testing for test_ui_panels
SchrodingersGat Oct 26, 2024
95d6bcb
Unit test for dashboard item API endpoint
SchrodingersGat Oct 27, 2024
c939ecc
Merge branch 'master' into dashboard-refactor
SchrodingersGat Oct 27, 2024
a0f01cc
Update comment
SchrodingersGat Oct 27, 2024
6deaabe
Adjust playwright tests
SchrodingersGat Oct 27, 2024
b23abd3
More playwright fixes
SchrodingersGat Oct 27, 2024
7af4d39
Hide barcode scanning options if disabled
SchrodingersGat Oct 28, 2024
77f953b
Tweak dashboard widget
SchrodingersGat Oct 28, 2024
df01929
Merge branch 'master' into dashboard-refactor
SchrodingersGat Oct 28, 2024
7b4e062
Merge branch 'master' into dashboard-refactor
SchrodingersGat Oct 28, 2024
f95ce20
Merge branch 'master' into dashboard-refactor
SchrodingersGat Oct 29, 2024
1069b00
Fix custom panel title
SchrodingersGat Oct 29, 2024
eab47ad
Update documentation around UIMixin class
SchrodingersGat Oct 29, 2024
5b4c861
Merge branch 'dashboard-refactor' of github.com:SchrodingersGat/Inven…
SchrodingersGat Oct 29, 2024
bb5555f
Cleanup
SchrodingersGat Oct 29, 2024
b6a0140
Additional docs
SchrodingersGat Oct 29, 2024
1db9221
Add icon def for 'error' ModelType
SchrodingersGat Oct 29, 2024
d71e033
Add error boundary to TemplateEditor component
SchrodingersGat Oct 30, 2024
8c44055
Fix so that it works with template editors and previews again
SchrodingersGat Oct 30, 2024
905cbe6
Merge remote-tracking branch 'origin/master' into dashboard-refactor
SchrodingersGat Oct 31, 2024
f382f61
Tweak error messages
SchrodingersGat Oct 31, 2024
f0d95dd
API unit test fixes
SchrodingersGat Oct 31, 2024
9d7e33f
Merge branch 'master' into dashboard-refactor
SchrodingersGat Nov 1, 2024
450cc6a
Unit test fix
SchrodingersGat Nov 1, 2024
cff6131
More unit test fixes
SchrodingersGat Nov 1, 2024
2e3105e
Playwright test tweaks
SchrodingersGat Nov 1, 2024
a18d3f3
Adjust error messages
SchrodingersGat Nov 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 106 additions & 62 deletions docs/docs/extend/plugins/ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,37 @@ title: User Interface Mixin

## User Interface Mixin

The *User Interface* mixin class provides a set of methods to implement custom functionality for the InvenTree web interface.
The `UserInterfaceMixin` class provides a set of methods to implement custom functionality for the InvenTree web interface.

### Enable User Interface Mixin

To enable user interface plugins, the global setting `ENABLE_PLUGINS_INTERFACE` must be enabled, in the [plugin settings](../../settings/global.md#plugin-settings).

## Plugin Context
## Custom UI Features

When rendering certain content in the user interface, the rendering functions are passed a `context` object which contains information about the current page being rendered. The type of the `context` object is defined in the `PluginContext` file:
The InvenTree user interface functionality can be extended in various ways using plugins. Multiple types of user interface *features* can be added to the InvenTree user interface.

{{ includefile("src/frontend/src/components/plugins/PluginContext.tsx", title="Plugin Context", fmt="javascript") }}
The entrypoint for user interface plugins is the `UserInterfaceMixin` class, which provides a number of methods which can be overridden to provide custom functionality. The `get_ui_features` method is used to extract available user interface features from the plugin:

## Custom Panels
::: plugin.base.ui.mixins.UserInterfaceMixin.get_ui_features
options:
show_bases: False
show_root_heading: False
show_root_toc_entry: False
show_sources: True
summary: False
members: []

Many of the pages in the InvenTree web interface are built using a series of "panels" which are displayed on the page. Custom panels can be added to these pages, by implementing the `get_ui_panels` method:
Note here that the `get_ui_features` calls other methods to extract the available features from the plugin, based on the requested feature type. These methods can be overridden to provide custom functionality.

::: plugin.base.ui.mixins.UserInterfaceMixin.get_ui_panels
!!! info "Implementation"
Your custom plugin does not need to override the `get_ui_features` method. Instead, override one of the other methods to provide custom functionality.

### UIFeature Return Type

The `get_ui_features` method should return a list of `UIFeature` objects, which define the available user interface features for the plugin. The `UIFeature` class is defined as follows:

::: plugin.base.ui.mixins.UIFeature
options:
show_bases: False
show_root_heading: False
Expand All @@ -29,71 +43,82 @@ Many of the pages in the InvenTree web interface are built using a series of "pa
summary: False
members: []

The custom panels can display content which is generated either on the server side, or on the client side (see below).
Note that the *options* field contains fields which may be specific to a particular feature type - read the documentation below on each feature type for more information.

### Server Side Rendering
### Dynamic Feature Loading

The panel content can be generated on the server side, by returning a 'content' attribute in the response. This 'content' attribute is expected to be raw HTML, and is rendered directly into the page. This is particularly useful for displaying static content.
Each of the provided feature types can be loaded dynamically by the plugin, based on the information provided in the API request. For example, the plugin can choose to show or hide a particular feature based on the user permissions, or the current state of the system.

Server-side rendering is simple to implement, and can make use of the powerful Django templating system.
For examples of this dynamic feature loading, refer to the [sample plugin](#sample-plugin) implementation which demonstrates how to dynamically load custom panels based on the provided context.

Refer to the [sample plugin](#sample-plugin) for an example of how to implement server side rendering for custom panels.
### Javascript Source Files

**Advantages:**
The rendering function for the custom user interface features expect that the plugin provides a Javascript source file which contains the necessary code to render the custom content. The path to this file should be provided in the `source` field of the `UIFeature` object.

- Simple to implement
- Can use Django templates to render content
- Has access to the full InvenTree database, and content not available on the client side (via the API)
Note that the `source` field can include the name of the function to be called (if this differs from the expected default function name).

**Disadvantages:**
For example:

- Content is rendered on the server side, and cannot be updated without a page refresh
- Content is not interactive
```
"source": "/static/plugins/my_plugin/my_plugin.js:my_custom_function"
```

### Client Side Rendering
## Available UI Feature Types

The panel content can also be generated on the client side, by returning a 'source' attribute in the response. This 'source' attribute is expected to be a URL which points to a JavaScript file which will be loaded by the client.
The following user interface feature types are available:

Refer to the [sample plugin](#sample-plugin) for an example of how to implement client side rendering for custom panels.
### Dashboard Items

#### Panel Render Function
The InvenTree dashboard is a collection of "items" which are displayed on the main dashboard page. Custom dashboard items can be added to the dashboard by implementing the `get_ui_dashboard_items` method:

The JavaScript file must implement a `renderPanel` function, which is called by the client when the panel is rendered. This function is passed two parameters:
::: plugin.base.ui.mixins.UserInterfaceMixin.get_ui_dashboard_items
options:
show_bases: False
show_root_heading: False
show_root_toc_entry: False
show_sources: True
summary: False
members: []

- `target`: The HTML element which the panel content should be rendered into
- `context`: A dictionary of context data which can be used to render the panel content
#### Dashboard Item Options

The *options* field in the returned `UIFeature` object can contain the following properties:

**Example**
::: plugin.base.ui.mixins.CustomDashboardItemOptions
options:
show_bases: False
show_root_heading: False
show_root_toc_entry: False
show_sources: True
summary: False
members: []

```javascript
export function renderPanel(target, context) {
target.innerHTML = "<h1>Hello, world!</h1>";
}
```
#### Source Function

#### Panel Visibility Function
The frontend code expects a path to a javascript file containing a function named `renderDashboardItem` which will be called to render the custom dashboard item. Note that this function name can be overridden by appending the function name in the `source` field of the `UIFeature` object.

The JavaScript file can also implement a `isPanelHidden` function, which is called by the client to determine if the panel is displayed. This function is passed a single parameter, *context* - which is the same as the context data passed to the `renderPanel` function.
#### Example

The `isPanelHidden` function should return a boolean value, which determines if the panel is displayed or not, based on the context data.
Refer to the [sample plugin](#sample-plugin) for an example of how to implement server side rendering for custom panels.

If the `isPanelHidden` function is not implemented, the panel will be displayed by default.
### Panels

**Example**
Many of the pages in the InvenTree web interface are built using a series of "panels" which are displayed on the page. Custom panels can be added to these pages, by implementing the `get_ui_panels` method:

```javascript
export function isPanelHidden(context) {
// Only visible for active parts
return context.model == 'part' && context.instance?.active;
}
```
::: plugin.base.ui.mixins.UserInterfaceMixin.get_ui_panels
options:
show_bases: False
show_root_heading: False
show_root_toc_entry: False
show_sources: True
summary: False
members: []

## Custom UI Functions
#### Panel Options

User interface plugins can also provide additional user interface functions. These functions can be provided via the `get_ui_features` method:
The *options* field in the returned `UIFeature` object can contain the following properties:

::: plugin.base.ui.mixins.UserInterfaceMixin.get_ui_features
::: plugin.base.ui.mixins.CustomPanelOptions
options:
show_bases: False
show_root_heading: False
Expand All @@ -102,36 +127,55 @@ User interface plugins can also provide additional user interface functions. The
summary: False
members: []

::: plugin.samples.integration.user_interface_sample.SampleUserInterfacePlugin.get_ui_features
options:
show_bases: False
show_root_heading: False
show_root_toc_entry: False
show_source: True
members: []
#### Source Function

The frontend code expects a path to a javascript file containing a function named `renderPanel` which will be called to render the custom panel. Note that this function name can be overridden by appending the function name in the `source` field of the `UIFeature` object.

Currently the following functions can be extended:
#### Example

### Template editors
Refer to the [sample plugin](#sample-plugin) for an example of how to implement server side rendering for custom panels.

The `template_editor` feature type can be used to provide custom template editors.
### Template Editors

**Example:**
The `get_ui_template_editors` feature type can be used to provide custom template editors.

{{ includefile("src/backend/InvenTree/plugin/samples/static/plugin/sample_template.js", title="sample_template.js", fmt="javascript") }}
::: plugin.base.ui.mixins.UserInterfaceMixin.get_ui_template_editors
options:
show_bases: False
show_root_heading: False
show_root_toc_entry: False
show_sources: True
summary: False
members: []

### Template previews

The `template_preview` feature type can be used to provide custom template previews. For an example see:
The `get_ui_template_previews` feature type can be used to provide custom template previews:

::: plugin.base.ui.mixins.UserInterfaceMixin.get_ui_template_previews
options:
show_bases: False
show_root_heading: False
show_root_toc_entry: False
show_sources: True
summary: False
members: []

## Plugin Context

When rendering certain content in the user interface, the rendering functions are passed a `context` object which contains information about the current page being rendered. The type of the `context` object is defined in the `PluginContext` file:

{{ includefile("src/frontend/src/components/plugins/PluginContext.tsx", title="Plugin Context", fmt="javascript") }}

This context data can be used to provide additional information to the rendering functions, and can be used to dynamically render content based on the current state of the system.

**Example:**
### Additional Context

{{ includefile("src/backend/InvenTree/plugin/samples/static/plugin/sample_template.js", title="sample_template.js", fmt="javascript") }}
Note that additional context can be passed to the rendering functions by adding additional key-value pairs to the `context` field in the `UIFeature` return type (provided by the backend via the API). This field is optional, and can be used at the discretion of the plugin developer.

## Sample Plugin

A sample plugin which implements custom user interface functionality is provided in the InvenTree source code:
A sample plugin which implements custom user interface functionality is provided in the InvenTree source code, which provides a full working example of how to implement custom user interface functionality.

::: plugin.samples.integration.user_interface_sample.SampleUserInterfacePlugin
options:
Expand Down
5 changes: 4 additions & 1 deletion src/backend/InvenTree/InvenTree/api_version.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
"""InvenTree API version information."""

# InvenTree API version
INVENTREE_API_VERSION = 276
INVENTREE_API_VERSION = 277

"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""


INVENTREE_API_TEXT = """

v277 - 2024-11-01 : https://github.com/inventree/InvenTree/pull/8278
- Allow build order list to be filtered by "outstanding" (alias for "active")

v276 - 2024-10-31 : https://github.com/inventree/InvenTree/pull/8403
- Adds 'destination' field to the PurchaseOrder model and API endpoints

Expand Down
3 changes: 3 additions & 0 deletions src/backend/InvenTree/build/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ class Meta:

active = rest_filters.BooleanFilter(label='Build is active', method='filter_active')

# 'outstanding' is an alias for 'active' here
outstanding = rest_filters.BooleanFilter(label='Build is outstanding', method='filter_active')

def filter_active(self, queryset, name, value):
"""Filter the queryset to either include or exclude orders which are active."""
if str2bool(value):
Expand Down
4 changes: 4 additions & 0 deletions src/backend/InvenTree/build/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,10 @@ def test_create_delete_output(self):
# Now, let's delete each build output individually via the API
outputs = bo.build_outputs.all()

# Assert that each output is currently in production
for output in outputs:
self.assertTrue(output.is_building)

delete_url = reverse('api-build-output-delete', kwargs={'pk': 1})

response = self.post(
Expand Down
4 changes: 3 additions & 1 deletion src/backend/InvenTree/common/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -895,7 +895,9 @@ def run_validator(self, validator):
except ValidationError as e:
raise e
except Exception:
raise ValidationError({'value': _('Invalid value')})
raise ValidationError({
'value': _('Value does not pass validation checks')
})

def validate_unique(self, exclude=None):
"""Ensure that the key:value pair is unique. In addition to the base validators, this ensures that the 'key' is unique, using a case-insensitive comparison.
Expand Down
12 changes: 9 additions & 3 deletions src/backend/InvenTree/order/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,9 @@ def get_queryset(self, *args, **kwargs):
"""Return the annotated queryset for this endpoint."""
queryset = super().get_queryset(*args, **kwargs)

queryset = queryset.prefetch_related('supplier', 'lines')
queryset = queryset.prefetch_related(
'supplier', 'project_code', 'lines', 'responsible'
)

queryset = serializers.PurchaseOrderSerializer.annotate_queryset(queryset)

Expand Down Expand Up @@ -671,7 +673,9 @@ def get_queryset(self, *args, **kwargs):
"""Return annotated queryset for this endpoint."""
queryset = super().get_queryset(*args, **kwargs)

queryset = queryset.prefetch_related('customer', 'lines')
queryset = queryset.prefetch_related(
'customer', 'responsible', 'project_code', 'lines'
)

queryset = serializers.SalesOrderSerializer.annotate_queryset(queryset)

Expand Down Expand Up @@ -1244,7 +1248,9 @@ def get_queryset(self, *args, **kwargs):
"""Return annotated queryset for this endpoint."""
queryset = super().get_queryset(*args, **kwargs)

queryset = queryset.prefetch_related('customer')
queryset = queryset.prefetch_related(
'customer', 'lines', 'project_code', 'responsible'
)

queryset = serializers.ReturnOrderSerializer.annotate_queryset(queryset)

Expand Down
29 changes: 29 additions & 0 deletions src/backend/InvenTree/plugin/base/integration/SettingsMixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,32 @@ def check_settings(self):
return PluginSetting.check_all_settings(
settings_definition=self.settings, plugin=self.plugin_config()
)

def get_settings_dict(self) -> dict:
"""Return a dictionary of all settings for this plugin.

- For each setting, return <key>: <value> pair.
- If the setting is not defined, return the default value (if defined).

Returns:
dict: Dictionary of all settings for this plugin
"""
from plugin.models import PluginSetting

keys = self.settings.keys()

settings = PluginSetting.objects.filter(
plugin=self.plugin_config(), key__in=keys
)

settings_dict = {}

for setting in settings:
settings_dict[setting.key] = setting.value

# Add any missing settings
for key in keys:
if key not in settings_dict:
settings_dict[key] = self.settings[key].get('default')

return settings_dict
Loading
Loading