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

Unify feature check for organization/project #8920

Merged
merged 24 commits into from Apr 5, 2023
Merged

Conversation

stsewd
Copy link
Member

@stsewd stsewd commented Feb 14, 2022

We are overriding classes and skipping tests con .com just because on .com we require a subscription for some features.
Now that we have subs on .org we can share more code between both sites. I have added a new setting to check if features should always be present or if we should check for a subscription.

Refs #9313

project,
type=PlanFeature.TYPE_CONCURRENT_BUILDS,
default=settings.RTD_MAX_CONCURRENT_BUILDS,
)
Copy link
Member Author

Choose a reason for hiding this comment

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

we had an override just to check for the value from the plan on .com.

Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this method contain all the logic? I mean, also check for project.max_concurrent_builds or max_concurrent_organization as well?

Otherwise, it's only being called for the default value, but it won't affect the project's value.

Copy link
Member

Choose a reason for hiding this comment

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

It seems my suggestion is already applied, right?

@stsewd stsewd marked this pull request as ready for review February 16, 2022 20:30
@stsewd stsewd requested a review from a team as a code owner February 16, 2022 20:30
Copy link
Member

@humitos humitos left a comment

Choose a reason for hiding this comment

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

This looks good as a start. However, I think it needs to encapsulate more logic in the .get_feature_value() so we can trust 100% on it instead of mixing it with some extra logic in other places (e.g. max concurrent builds)

I'd like to do PlanFeature.objects.get_feature_value(project, type=PlanFeature.TYPE_CONCURRENT_BUILDS, default=settings.RTD_MAX_CONCURRENT_BUILDS) and don't worry if the project is under an organization or not and if it has a particular max concurrent defined at the project level. I'd like to rely on the .get_feature_value() to handle all that logic for me.

BTW, it would have been good to have a better description in the PR to understand what this code should do to reduce guessing.

readthedocs/settings/base.py Outdated Show resolved Hide resolved
readthedocs/organizations/views/private.py Outdated Show resolved Hide resolved
project,
type=PlanFeature.TYPE_CONCURRENT_BUILDS,
default=settings.RTD_MAX_CONCURRENT_BUILDS,
)
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this method contain all the logic? I mean, also check for project.max_concurrent_builds or max_concurrent_organization as well?

Otherwise, it's only being called for the default value, but it won't affect the project's value.

readthedocs/projects/views/private.py Outdated Show resolved Hide resolved
Comment on lines 230 to 234
if not settings.RTD_ALL_FEATURES_ENABLED:
feature = self.get_feature(obj, type)
if feature:
return feature.value
return default
Copy link
Member

Choose a reason for hiding this comment

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

Here is where I think it needs to encapsulate more logic, so we can rely 100% on it without mixing the .get_feature_value with other logic in the code.

@stsewd
Copy link
Member Author

stsewd commented Mar 7, 2022

I think it needs to encapsulate more logic in the .get_feature_value() so we can trust 100% on it instead of mixing it with some extra logic in other places (e.g. max concurrent builds)

Not really sure about that, concurrent build is the only one, and is very specific about the attributes and order that should be checked. It makes more sense to have that on project model class.

@humitos
Copy link
Member

humitos commented Mar 8, 2022

Not really sure about that, concurrent build is the only one, and is very specific about the attributes and order that should be checked.

Yeah, even if it's the only one now, I think it could be better to handle each special case we may have in the future inside the .get_feature_value() so all logic related to "getting the real value for a feature" is in the same place instead of in multiple parts of the codebase --producing, maybe, different logic/results as it has to be duplicated.

@stsewd
Copy link
Member Author

stsewd commented Mar 9, 2022

all logic related to "getting the real value for a feature" is in the same place instead of in multiple parts of the codebase --producing, maybe, different logic/results as it has to be duplicated.

I'm not really convince about having that special case there, the logic is already encapsulated in the project queryset

def max_concurrent_builds(self, project):
.

If someone else thinks that's fine, I can change it.

@humitos
Copy link
Member

humitos commented Mar 9, 2022

the logic is already encapsulated in the project queryset

That's my point here. For all the PlanFeature(s) it's enough to do PlanFeature.objects.get_feature_value but for this feature in particular, max concurrent builds, you have to do something different: call the queryset. I'm saying that it would be good to put the logic of the queryset inside the .get_feature_value so there is no difference among any of the plan features to get the final feature value.

@stale
Copy link

stale bot commented May 2, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the Status: stale Issue will be considered inactive soon label May 2, 2022
@stsewd stsewd removed the Status: stale Issue will be considered inactive soon label May 3, 2022
@stsewd stsewd marked this pull request as draft November 22, 2022 18:31
@stsewd
Copy link
Member Author

stsewd commented Nov 23, 2022

I have changed this slightly, instead of having a setting to enable all features, now we have a setting to enable a set of features, this is for users that don't have a subscription (.org), on .com this will {} to rely entirely on the features from the subscription.

I'd like to merge this, so we can continue with the djstripe migration, this is useful since we would have only one place where to change the queryset/lookup logic. This also removes a lot of overrides on .com (and probably we can remove more!).

The only blocker before was a disagreement on #8920 (comment), I still don't think we should merge that logic there (we only want to know the value of the feature, not the value of the org/project), and the organization.max_concurrent_builds probably should be removed and rely only on the value from the subscription (I think we added that in order to sell more builders, but we now can have that value in the sub itself), and we already have that logic encapsulated in the queryset #8920 (comment).

@stsewd stsewd marked this pull request as ready for review November 23, 2022 16:56
@stsewd stsewd requested a review from humitos November 23, 2022 16:56
@ericholscher
Copy link
Member

Looks like there's an update here, if you remember much from the past to review it @humitos

Copy link
Member

@humitos humitos left a comment

Choose a reason for hiding this comment

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

Looks good. I left some minor details to be considered and make the code more readable, basically.

Comment on lines +10 to +23
TYPE_CNAME = "cname"
TYPE_CDN = "cdn"
TYPE_SSL = "ssl"
TYPE_SUPPORT = "support"

TYPE_PRIVATE_DOCS = "private_docs"
TYPE_EMBED_API = "embed_api"
TYPE_SEARCH_ANALYTICS = "search_analytics"
TYPE_PAGEVIEW_ANALYTICS = "pageviews_analytics"
TYPE_CONCURRENT_BUILDS = "concurrent_builds"
TYPE_SSO = "sso"
TYPE_CUSTOM_URL = "urls"
TYPE_AUDIT_LOGS = "audit-logs"
TYPE_AUDIT_PAGEVIEWS = "audit-pageviews"
Copy link
Member

Choose a reason for hiding this comment

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

Is there any reason why these are prefixed with TYPE_ instead of FEATURE_? TYPE_EMBED_API does not make too much sense to me, but FEATURE_EMBED_API does.

In places like this one, it will be more readable: https://github.com/readthedocs/readthedocs.org/pull/8920/files#diff-e7f36d6ba29b1ae0d1c6e12781ba3f54dc4cb434cf07057f73e2f5d57b545ae9R166

Copy link
Contributor

Choose a reason for hiding this comment

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

This is also why I've preferred model constants are defined on the models themselves. The added context and readability of Feature.TYPE_EMBED_API or Feature.EMBED_API is quite nicer than a list of constants.

Copy link
Member Author

Choose a reason for hiding this comment

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

This is a place where I'd love to use enums instead p:

Comment on lines 1161 to +1166
def _is_enabled(self, project):
"""Should we show search analytics for this project?"""
return True


class SearchAnalytics(SettingsOverrideObject):
_default_class = SearchAnalyticsBase
return PlanFeature.objects.has_feature(
project,
type=self.feature_type,
)
Copy link
Member

Choose a reason for hiding this comment

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

This method should go in a mixin since it's shared among multiple classes.

Comment on lines +296 to +300
def _is_feature_enabled(self, organization):
return PlanFeature.objects.has_feature(
organization,
type=self.feature_type,
)
Copy link
Member

Choose a reason for hiding this comment

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

This is the same method as _is_enabled. However, it uses organization= instead of project= and it's named differently. We should decide whether naming it _is_feature_enabled or _is_enabled to keep consistency.

project,
type=PlanFeature.TYPE_CONCURRENT_BUILDS,
default=settings.RTD_MAX_CONCURRENT_BUILDS,
)
Copy link
Member

Choose a reason for hiding this comment

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

It seems my suggestion is already applied, right?

@ericholscher
Copy link
Member

@stsewd Is this ready to get merged as well?

@stsewd
Copy link
Member Author

stsewd commented Feb 14, 2023

@stsewd Is this ready to get merged as well?

Mostly I guess, need to update the PR, maybe revising using enums at #8920 (comment).

@humitos
Copy link
Member

humitos commented Mar 7, 2023

@stsewd It would be good to move forward with this PR since the work is already done and reviewed. Can you update it to be in a meargeable state?

@stsewd stsewd merged commit 6edc096 into main Apr 5, 2023
7 checks passed
@stsewd stsewd deleted the unify-feature-check branch April 5, 2023 18:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants