-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Introduce EnabledByDependencyOnly flag for features to allow the feature's state to be auto (enabled or disabled) #12281
Introduce EnabledByDependencyOnly flag for features to allow the feature's state to be auto (enabled or disabled) #12281
Conversation
Hmm but So a non listable dependency would be enabled by a dependent, but what happens if you disable all dependents, the dependency would be still enabled and we could not disable it as it is non listable. Would need to check if there is any remaining dependent, if not we could auto disable the dependency. Hmm and if this dependency has other dependencies ;) |
Hmm maybe OC.Features settings where we could add/remove which features are non listable |
@jtkech that settings is done in the Manifest as far as I know. if there other way to define settings to the feature? But may be you have better ideas. |
Did you see this part of my previous comment?
|
@jtkech sorry I missed that first comment. I did not scroll up when I saw the commend. Good point. I agree with you, we'll need a way to check for dependent. maybe hook into features events after a module is disable, then disable features. not sure how the right approach there. When a module is disable, check if any of it's dependencies is listable. If any, then check it any active feature still need is or disable it automatically |
@jtkech I added logic into the Note: I think we should name this flag something other than |
…encyOnly. Ready to go
@jtkech this PR is ready for review. I also updated the title/description with the used terminology to eliminate confusion. The core change here is in |
First just for info I already didn't fully like how
So, looks like we already use manifest properties that are not full feature info states but more like metadata used by higher level services, but without saying yet that is good to add another one ;) That said I took a look at the code, before going further I think that you don't need all the recursive stuff, Updated: As an additional note, just thought about the recipe |
@jtkech I’ll update the ShellFeaturesManager to remove the recursive stuff. Thanks for the tip there. Are you saying that IShellFeatureManager isn’t the lowest level to control features? If not, what would be the lowest level to control features? I assumed that it’s the lowest level and is the right place for adding my logic. My logic should be added to the lowest level so it can’t be bypassed. Also, there should be a service that gets called after the shell becomes running that enabled all the Also, if the IFeatureValidator isn’t executed at the lowest level, that means the FeaturesProfile is not also secure as it should be bullet proof |
@MikeAlhayek Sorry didn't have so much time Did you see this part of my comment? Meaning that half of the logic is already implemented.
We already manage missing dependencies, moreover we already manage remaining dependents with So not yet sure at which level the But about the logic I think we only need to always add all Note: If at least at the |
@jtkech i have not had much time to look at the code. I should have say after tomorrow. However, it’s my understanding that your concerned about adding the extra property since it introduce lots of changes. I was thinking to wait on more feedback on that front before worrying about the property implementation. Are you okay with adding EnabledByDependencyOnly as I already did? |
@MikeAlhayek So read my previous comment, then I did a quick test that seems to work. So as said features are already auto enabled, the new thing would be to auto disabled them (and not list them in the admin UI), not sure about the name but for now let's call it So for testing I populated a Then in
Then just after in 2 places just add them to the
So the logic can be done in 2 or 3 lines of code ;) |
@jtkech I updated the code by only adding all features that are Updated
with
If you agree, maybe I can make this part of a separate PR unless you want it part of this PR too. Thoughts? |
@@ -35,8 +35,8 @@ public class ShellDescriptorFeaturesManager : IShellDescriptorFeaturesManager | |||
IEnumerable<IFeatureInfo> featuresToDisable, IEnumerable<IFeatureInfo> featuresToEnable, bool force) | |||
{ | |||
var featureEventHandlers = ShellScope.Services.GetServices<IFeatureEventHandler>(); | |||
|
|||
var enabledFeatureIds = _extensionManager.GetFeatures() | |||
var allFeatures = _extensionManager.GetFeatures().ToHashSet(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.ToHashSet()
can be removed, IFeatureInfo
is not IEquatable
, would be only compared by ref, okay if there are no cloned instances but anyway _extensionManager
already returns a cached and distinct collection. In fact we only use .ToHashSet()
to ensure distincts feature ids strings when we process the collection later on.
Here I would only extract the enabledFeatures
to be used later on, this to only try to disable the EnabledByDependencyOnly
features retrieved from the enabled features only (see below).
var enabledFeatures = _extensionManager.GetFeatures()
.Where(f => shellDescriptor.Features.Any(sf => sf.Id == f.Id));
var enabledFeatureIds = enabledFeatures.Select(f => f.Id).ToHashSet();
@@ -50,6 +50,8 @@ public class ShellDescriptorFeaturesManager : IShellDescriptorFeaturesManager | |||
var allFeaturesToDisable = featuresToDisable | |||
.Where(f => !alwaysEnabledIds.Contains(f.Id)) | |||
.SelectMany(feature => GetFeaturesToDisable(feature, enabledFeatureIds, force)) | |||
// Always attempt to disable EnabledByDependencyOnly features to ensure we auto disable any feature that is no longer needed | |||
.Union(allFeatures.Where(f => f.EnabledByDependencyOnly)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And then here only try to disable the EnabledByDependencyOnly
features that are currently enabled. Also the logic here is to do the Union()
before GetFeaturesToDisable()
(that checks dependents) is applied, even if missing dependencies are also managed in ShellDescriptorManager
.
var allFeaturesToDisable = featuresToDisable
// Try to disable 'EnabledByDependencyOnly' features if they have no more dependents.
.Union(enabledFeatures.Where(f => f.EnabledByDependencyOnly))
.Where(f => !alwaysEnabledIds.Contains(f.Id))
.SelectMany(feature => GetFeaturesToDisable(feature, enabledFeatureIds, force))
.Distinct()
.Reverse()
.ToList();
@T["Disabled"] | ||
} | ||
</span> | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One suggestion is to do it below by using button tag with the bootstrap disabled attribute, so that it keeps the same rendering flow.
@if (feature.ByDependencyOnly || feature.IsAlwaysEnabled)
{
@if (!feature.IsEnabled)
{
<button class="btn btn-primary btn-sm" disabled>@T["Enable"]</button>
}
else
{
<button class="btn btn-danger btn-sm" disabled>@T["Disable"]</button>
}
}
else if (showEnable && !feature.IsEnabled)
{
<a id="[email protected](feature.Descriptor.Id)" ........ >@T["Enable"]</a>
}
else if (showDisable && feature.IsEnabled)
{
...
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jtkech i'll get these changes made and retest. Probably won't happen before tomorrow.
But what about my comment about adding featureValidators
check before installing features.
About Looks like the code you are suggesting doesn't prevent to enable a feature if not valid, installed features is just an history of all features that has been enabled even if currently disabled. But not sure we want to use So yes better to use a separate PR if you still want to suggest something around this. Idem, not sure if |
@jtkech there are couple of services that around feature management. I feel we should only be using one service to manage features for a given ShellDescriptor. So if we have a service that update the shell by enabling/disabling features, that service will validate the given feature ids and then process the update. I will try to submit another PR once we are done with this one just as a concept and we'll see how you feel about it. The reason I think we should be validating using the validator is for DefaultTenant and also the TeantFeatures. I think that should be validated in the services. These validator should be part of the shellFeaturesManagers as it should be responsible of validating features before enabling them just like we do with the AlwaysEnabled features. And for the record, I still think "IsOnDemand" or "EnabledOnDemand" is a better property name than "EnabledByDependenyOnly". Because what we are trying to do here is provide a feature that is available on demand which is which it's needed. |
|
@jtkech thank you for the detailed response. When I suggested the name I personally don't think the config settings will be very useful. But could be missing the point. I think having the feature auto managed by the shell manager is much more powerful since the user does not have to worry about its state what so ever. It'll be enable when needed and disabled when it's not used. This will help also freeing up resources when they are no longer needed "unlike always available." These two have different use case and each provide different behavior. Updated Anyway, once we are done with this PR, I'll attempt to refactor the code in a separate PR and see if the refactoring make sense or not. Can you do me a favor here please? Can you please think about using the name "EnabledOnDemand" instead of "EnabledByDependencyOnly"? I think the term "EnabledOnDemand" is a much better fit and explain the behavior better. |
Okay, hmm one "problem" is that the controller/routes are defined in the Anyway I let you see at the triage meeting (not at a good time for me to meet) if this need makes sense, if so at which level the property should be defined and at which level the implemention should be done. If we use a new manifest property, maybe a more generic as Let me know if this can be clarified, so that I can review again the PR for the implementation details. Hmm, maybe a kind of
Updated: Or the same |
@jtkech about the change request. I personally think I like that change in the UI. It becomes confusing. The idea of having a feedback at a meta data in a badge is that it would provide the info away from the action. Also, the only way the As for my example, I am going to create an example in a separate branch to show you what I mean. I'll ping you when I am done with it. |
@jtkech For now the push notification will just log to the log file just for the sake to f. If you enable the "OC.Notification.Email" be sure to also configure your email settings to see the email. You could configure the email to go to a local folder for now. Finally, if you actually you want notification, edit your user profile and check the notification When you run the project and enabled at least one type of notification, you should see a new menu item "Example >> Notify User" you can use to test out the notifications. Please let me know your thought about this sample project Here is a screenshot of a real example One more thing to considerIt would be a huge to add an attribute to target tags. For example With that, we can implement a feature that will look for at least one other feature with the tag "push-notification" before it become available. For example, we can add the interface in
|
During triage suggestions were to add the disable/enable button but in a disabled state, for this flag and for the "always enabled" one, to keep consistency between features, but add a tooltip to explain why the disable/enable button can't be clicked. Agree with the issue and solution. |
@sebastienros I changed the button as per your and @jtkech suggestion. |
Just retried, it works assuming the The main point is that you didn't take into account my comment #12281 (comment) saying to only try to disable the This would have prevented useless feature events handlers calls just after.
Finally the prop is still named
Can you do a PR for the above tweaks? |
@jtkech yes. I tested with the |
No, @sebastienros merged it which is good ;) The main point is to retrieve the OnDemand but only from the enabled features, it will prevent to always call the disabling handlers for all OnDemand features. |
@jtkech I'll add these changes with a new PR. |
Fix #12279
A feature with
EnabledByDependencyOnly
, will automatically get enabled/disabled as needed by other features.Let's say we have the following features
The feature
OC.Notification
will automatically get enabled when any ofOC.Notification.Email
,OC.Notification.Push
, orOC.Notification.Web
are enabled by the user.At the same time,
OC.Notification
will automatically get disabled when all ofOC.Notification.Email
,OC.Notification.Push
, andOC.Notification.Web
are disabled.The key difference between
EnabledByDependencyOnly
andIsAlwaysEnabled
is that the latter will always be enabled regardless whether it's needed or not.Checkout this screen-cast to the end to see the full behavior in action.