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

Add support for changing hook order #4186

Merged
merged 4 commits into from
Feb 22, 2022

Conversation

kantlivelong
Copy link
Contributor

@kantlivelong kantlivelong commented Jul 16, 2021

What does this PR do and why is it necessary?

Allows the user to override the hook order value for plugins. Useful in cases where one plugin hook might need to be executed before another but they have conflicting order values.

How was it tested? How can it be tested by the reviewer?

Config:
Each plugin can have an additional key called '_hook_order_overrides' which contains a subkey for each hook ID.

Example:

  softwareupdate:
    _hook_order_overrides:
      octoprint.access.permissions: 11
      octoprint.events.register_custom_events: 99

UI:
Settings -> Plugin Manager -> Hook Order (See screenshot)

Any background context you want to provide?

What are the relevant tickets if any?

Screenshots

image

Further notes

I'm sure the JS side of things can be optimized better. Pointers appreciated.

I wasn't sure the best way to display what the computed default was in the UI. I just labeled it as "Definition". Originally I called it "Original" and displayed the order number instead of the value or "Default". Thoughts?

I used _hook_order_overrides as the config key name but if there is something better/shorter you'd prefer let me know. Same goes for the blueprint path or anything else for the matter.

There are two(2) TODO lines which maybe you can answer:

  1. https://github.com/kantlivelong/OctoPrint/blob/08a26d76bcd4f5a957658211e5cdb6cbb34e8079/src/octoprint/plugins/pluginmanager/static/js/pluginmanager.js#L584
  2. https://github.com/kantlivelong/OctoPrint/blob/08a26d76bcd4f5a957658211e5cdb6cbb34e8079/src/octoprint/plugins/pluginmanager/__init__.py#L717

@kantlivelong kantlivelong requested a review from foosel July 16, 2021 13:23
@github-actions github-actions bot added the approved Issue has been approved by the bot or manually for further processing label Jul 16, 2021
Copy link
Member

@foosel foosel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got a bunch of JS refactoring suggestions and other points.

@@ -1676,6 +1677,13 @@ def _activate_plugin(self, name, plugin):
)
continue

if not order:
order = 1000
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be a constant, e.g. DEFAULT_ORDER

@@ -1719,6 +1727,9 @@ def _deactivate_plugin(self, name, plugin):
)
continue

if not order:
order = 1000
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Constant

@@ -2167,11 +2178,12 @@ def sort_func(impl):
impl[0], sorting_context
)
)
sorting_value = None
sorting_value = 1000
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Constant

sorting_value = None
sorting_value = 1000
else:
sorting_value = 1000
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Constant

for hook in data["hooks"]:
# TODO: Force really needed?
self._settings.global_set_int(
["plugins", hook["id"], "_hook_order_overrides", hook["hook"]],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sanity check that id, hook and value are actually set on hook.

Comment on lines 2045 to 2052
for i in range(len(self._plugin_manager._plugin_hooks[hook])):
if self._plugin_manager._plugin_hooks[hook][i][1] == plugin:
self._plugin_manager._plugin_hooks[hook][i] = (
value,
plugin,
self._plugin_manager._plugin_hooks[hook],
)
return
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This really feels like it should be added to the core plugin manager class instead, as it heavily interacts with internal data structures

Comment on lines 2056 to 2073
for hook_name, hooks in self._plugin_manager._plugin_hooks.items():
order = dict(name=hook_name, hooks=[])
for order_current, plugin_id, _hook in hooks:
plugin_name = self._plugin_manager.plugins[plugin_id].name

_, order_original = self._plugin_manager._get_callback_and_order(
self._plugin_manager.plugins[plugin_id].hooks[hook_name]
)

order["hooks"].append(
dict(
id=plugin_id,
name=plugin_name,
order_current=order_current,
order_original=order_original,
)
)
result.append(order)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should prolly also be moved into the core plugin manager itself.

Comment on lines 757 to 765
.fail(function () {
self.requestError(true);
deferred.reject();
})
.done(function (data) {
self.requestError(false);
self.fromHookOrderResponse(data.hook_order);
deferred.resolveWith(data);
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really just for reasons of consistency: Swap order, first the success, then the failure case.

Comment on lines 585 to 590
for (i = 0; i < data.length; i++) {
for (ii = 0; ii < data[i]["hooks"].length; ii++) {
data[i]["hooks"][ii]["orderStaged"] = false;
data[i]["hooks"][ii]["orderTemp"] = false;
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_.each(data, function(entry) {
  if (!entry || !entry.hooks) return;
  _.each(entry.hooks, function(hook) {
    hook["orderStaged"] = false;
    hook["orderTemp"] = false;
  }
}

Comment on lines 1870 to 1883
for (var k = 0; k < self.hookOrder().length; k++) {
var hookGroup = self.hookOrder()[k]["name"]();

for (var kk = 0; kk < self.hookOrder()[k]["hooks"]().length; kk++) {
var hook = self.hookOrder()[k]["hooks"]()[kk];
if (hook["orderStaged"]()) {
changes.push({
hook: hookGroup,
id: hook["id"](),
value: hook["orderStaged"]()
});
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_.each(self.hookOrder(), function (entry) {
  var hookGroup = entry.name();
  _.each(entry.hooks(), function (hook) {
    if (hook.orderStaged()) {
      changes.push({
        hook: hookGroup,
        id: hook.id(),
        value: hook.orderStaged()
      });
    });
  });
});

@cp2004
Copy link
Member

cp2004 commented Jul 19, 2021

I wonder if this could do with a short sentence at the top explaining what it is for. For a plugin developer, it is clear as this is what we use to write the plugins. However, I suspect for many people this will be the first time they have heard of these 'hooks', and they don't have any idea what it is for. Maybe needs something that recommends leaving it as is unless advised/running into problems - this is less necessary, maybe if there's an explanation it would be enough. Don't want to fill up the UI with useless information :)

@foosel
Copy link
Member

foosel commented Jul 19, 2021

That's actually a good point. Should it maybe even be hidden behind a config flag?

@foosel
Copy link
Member

foosel commented Jul 19, 2021

Or - another thought - do we maybe need something like "Enable developer options" or "Enable advanced options" for the settings in general?

@kantlivelong
Copy link
Contributor Author

Or - another thought - do we maybe need something like "Enable developer options" or "Enable advanced options" for the settings in general?

Not a bad idea. IMO this would be considered an advanced option rather than dev. I could also break this out into it's own plugin instead of in Plugin Manager so that it can be disabled by default.

@foosel
Copy link
Member

foosel commented Jul 27, 2021

Advanced kinda settings are definitely something that needs some work first... the question is, should we target 1.7.0 for this here or not?

@kantlivelong
Copy link
Contributor Author

I think 1.7.0 makes sense. dev branch?

Also, do you have any input on the Further notes section of the PR?

@foosel
Copy link
Member

foosel commented Aug 3, 2021

1.7.0 is feature frozen as of yesterday (I hope to push out a first RC tomorrow, would have been today if I didn't have a doc appointment for my shoulder right in the middle that makes it difficult to plan around), so this will have to wait until 1.8.0, too many open points still and decisions to make regarding general approach for dev side stuff.

Will also give me time to take another look at the TODOs you marked up.

@foosel foosel added this to the 1.8.0 milestone Aug 3, 2021
@foosel foosel added needs some work There are some things to do before this PR can be merged targets maintenance The PR targets the maintenance branch labels Sep 9, 2021
@foosel
Copy link
Member

foosel commented Oct 11, 2021

@kantlivelong poke :)

@kantlivelong
Copy link
Contributor Author

@foosel OWW MY EYES!

I've been holding out for your comments on the Further Notes section. 😉

@foosel
Copy link
Member

foosel commented Nov 10, 2021

Sorry, that first got overlooked before my poke, and then needed some time after.

I'm sure the JS side of things can be optimized better. Pointers appreciated.

See the initial review.

I wasn't sure the best way to display what the computed default was in the UI. I just labeled it as "Definition". Originally I called it "Original" and displayed the order number instead of the value or "Default". Thoughts?

I'd seriously just got with "Computed" and displaying the original order number. That should transfer what this is best.

I used _hook_order_overrides as the config key name but if there is something better/shorter you'd prefer let me know. Same goes for the blueprint path or anything else for the matter.

Looking at the settings structure I think this should rather be plugins._hook_order_overrides.<plugin_id>.<hook> instead of plugins.<plugin_id>._hook_order_overrides.<hook>. That would match the use of this namespace for plugins._disabled and plugins._forcedCompatible. Naming wise, most of the settings use camelCase instead of snake_case (there are some exceptions I just saw, but they are rare). So it should probably rather be hookOrderOverrides. We could say that these are overrides is implicit, and thus we arrived at hookOrder.

There are two(2) TODO lines which maybe you can answer:

1. https://github.com/kantlivelong/OctoPrint/blob/08a26d76bcd4f5a957658211e5cdb6cbb34e8079/src/octoprint/plugins/pluginmanager/static/js/pluginmanager.js#L584

// TODO: Could be worth filtering this out as sorting certain hooks makes no difference.

You mean filtering by hook type? I'd honestly just say, treat them all the same, the less magic the better.

2. https://github.com/kantlivelong/OctoPrint/blob/08a26d76bcd4f5a957658211e5cdb6cbb34e8079/src/octoprint/plugins/pluginmanager/__init__.py#L717

# TODO: Force really needed?

Good question. Trying to remember when force was necessary, and IIRC it was when you are writing values directly that don't have a default (so not setting a whole dict on a key that has an empty dict as default, but traversing into the dict). In your case here, the path you are setting doesn't exist in the defaults, so a force is necessary.

@foosel
Copy link
Member

foosel commented Feb 17, 2022

@kantlivelong poke ;) I'm looking into starting to wrap up 1.8.0, so unless you do a race to the finish line here, this might get delayed into 1.9.0. No harm done, just want to give you a heads-up.

Default hook value changed from None to 1000
@kantlivelong
Copy link
Contributor Author

@foosel Thanks for the nudge. For now I think I'll just submit this without the UI portion. I've kept a copy of the UI branch at https://github.com/kantlivelong/OctoPrint/tree/hook_order_overrides_with_UI should I revisit it later or someone else want to take it on.

I've also adjusted according to your feedback 😄

  * fetch settings during platform initialization instead of in plugin core
  * rename from hook order to sorting order as it's also used for the
    sorting context of implementations
  * make sure settings are initialized empty in defaults
Copy link
Member

@foosel foosel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some missing docs and slightly refactored things to better match existing stuff.

@foosel foosel merged commit 80ffa88 into OctoPrint:maintenance Feb 22, 2022
@foosel foosel removed the needs some work There are some things to do before this PR can be merged label Feb 22, 2022
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 23, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
approved Issue has been approved by the bot or manually for further processing targets maintenance The PR targets the maintenance branch
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants