- 
          
- 
                Notifications
    You must be signed in to change notification settings 
- Fork 250
Implement GraphQL resolvers for project health metrics #1577
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
Changes from 24 commits
5cdbeb2
              d5b9dcb
              6875616
              bd26456
              fe32441
              2fe6ff7
              1b9bd60
              046b226
              76a05e4
              063c0fe
              25d6c3b
              0d0d573
              e97791e
              3a935b9
              26c5ce7
              c749700
              8c0173b
              dac0d22
              4442be0
              90bab4f
              019f614
              9ff593e
              2b27698
              9caedaa
              ade2191
              d7dfdf0
              File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| """OWASP Project Health Metrics Node.""" | ||
|  | ||
| import strawberry | ||
| import strawberry_django | ||
|  | ||
| from apps.owasp.models.project_health_metrics import ProjectHealthMetrics | ||
|  | ||
|  | ||
| @strawberry_django.type( | ||
| ProjectHealthMetrics, | ||
| fields=[ | ||
| "contributors_count", | ||
| "forks_count", | ||
| "is_funding_requirements_compliant", | ||
| "is_leader_requirements_compliant", | ||
| "open_issues_count", | ||
| "open_pull_requests_count", | ||
| "recent_releases_count", | ||
| "score", | ||
| "stars_count", | ||
| "unanswered_issues_count", | ||
| "unassigned_issues_count", | ||
| ], | ||
| ) | ||
| class ProjectHealthMetricsNode: | ||
| """Project health metrics node.""" | ||
|  | ||
| @strawberry.field | ||
| def age_days(self) -> int: | ||
| """Resolve project age in days.""" | ||
| return self.age_days | ||
|  | ||
| @strawberry.field | ||
| def last_commit_days(self) -> int: | ||
| """Resolve last commit age in days.""" | ||
| return self.last_commit_days | ||
|  | ||
| @strawberry.field | ||
| def last_pull_request_days(self) -> int: | ||
| """Resolve last pull request age in days.""" | ||
| return self.last_pull_request_days | ||
|  | ||
| @strawberry.field | ||
| def last_release_days(self) -> int: | ||
| """Resolve last release age in days.""" | ||
| return self.last_release_days | ||
|  | ||
| @strawberry.field | ||
| def owasp_page_last_update_days(self) -> int: | ||
| """Resolve OWASP page last update age in days.""" | ||
| return self.owasp_page_last_update_days | ||
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| # Generated by Django 5.2.1 on 2025-06-11 15:55 | ||
|  | ||
| from django.db import migrations, models | ||
|  | ||
|  | ||
| class Migration(migrations.Migration): | ||
| dependencies = [ | ||
| ("owasp", "0037_alter_projecthealthmetrics_project"), | ||
| ] | ||
|  | ||
| operations = [ | ||
| migrations.AddField( | ||
| model_name="projecthealthmetrics", | ||
| name="has_long_open_issues", | ||
| field=models.BooleanField(default=False, verbose_name="Has long open issues"), | ||
| ), | ||
| migrations.AddField( | ||
| model_name="projecthealthmetrics", | ||
| name="has_long_unanswered_issues", | ||
| field=models.BooleanField(default=False, verbose_name="Has long unanswered issues"), | ||
| ), | ||
| migrations.AddField( | ||
| model_name="projecthealthmetrics", | ||
| name="has_long_unassigned_issues", | ||
| field=models.BooleanField(default=False, verbose_name="Has long unassigned issues"), | ||
| ), | ||
| migrations.AddField( | ||
| model_name="projecthealthmetrics", | ||
| name="has_no_recent_commits", | ||
| field=models.BooleanField(default=False, verbose_name="Has no recent commits"), | ||
| ), | ||
| ] | 
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| # Generated by Django 5.2.1 on 2025-06-13 03:36 | ||
|  | ||
| from django.db import migrations | ||
|  | ||
|  | ||
| class Migration(migrations.Migration): | ||
| dependencies = [ | ||
| ("owasp", "0038_projecthealthmetrics_has_long_open_issues_and_more"), | ||
| ("owasp", "0040_alter_projecthealthmetrics_is_leader_requirements_compliant"), | ||
| ] | ||
|  | ||
| operations = [] | 
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,21 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Generated by Django 5.2.1 on 2025-06-13 05:54 | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| from django.db import migrations, models | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| class Migration(migrations.Migration): | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| dependencies = [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ("owasp", "0041_merge_20250613_0336"), | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| operations = [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| migrations.RemoveField( | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| model_name="projecthealthmetrics", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| name="has_no_recent_commits", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| migrations.AddField( | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| model_name="projecthealthmetrics", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| name="has_recent_commits", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| field=models.BooleanField(default=False, verbose_name="Has recent commits"), | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|          | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| operations = [ | |
| migrations.RemoveField( | |
| model_name="projecthealthmetrics", | |
| name="has_no_recent_commits", | |
| ), | |
| migrations.AddField( | |
| model_name="projecthealthmetrics", | |
| name="has_recent_commits", | |
| field=models.BooleanField(default=False, verbose_name="Has recent commits"), | |
| ), | |
| ] | |
| operations = [ | |
| migrations.RenameField( | |
| model_name="projecthealthmetrics", | |
| old_name="has_no_recent_commits", | |
| new_name="has_recent_commits", | |
| ), | |
| migrations.RunPython( | |
| code=lambda apps, _: apps | |
| .get_model("owasp", "ProjectHealthMetrics") | |
| .objects | |
| .update(has_recent_commits=models.F("has_recent_commits").bitwise_not()), | |
| reverse_code=migrations.RunPython.noop, | |
| ), | |
| ] | 
🤖 Prompt for AI Agents
In
backend/apps/owasp/migrations/0042_remove_projecthealthmetrics_has_no_recent_commits_and_more.py
around lines 11 to 21, the current migration removes the field
has_no_recent_commits and adds has_recent_commits, causing data loss and two
full-table rewrites. To fix this, replace the RemoveField and AddField with a
single RenameField operation to rename has_no_recent_commits to
has_recent_commits, then add a RunPython migration step that inverts the boolean
values to preserve the correct semantics. This approach keeps existing data
intact, reduces table rewrites to one, and clearly documents the data
transformation intent.
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| # Generated by Django 5.2.1 on 2025-06-13 07:44 | ||
|  | ||
| from django.db import migrations, models | ||
|  | ||
|  | ||
| class Migration(migrations.Migration): | ||
| dependencies = [ | ||
| ("owasp", "0042_remove_projecthealthmetrics_has_no_recent_commits_and_more"), | ||
| ] | ||
|  | ||
| operations = [ | ||
| migrations.RemoveField( | ||
| model_name="projecthealthmetrics", | ||
| name="has_recent_commits", | ||
| ), | ||
| migrations.AddField( | ||
| model_name="projecthealthmetrics", | ||
| name="has_no_recent_commits", | ||
| field=models.BooleanField(default=False, verbose_name="Has no recent commits"), | ||
| ), | ||
|         
                  ahmedxgouda marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved         
                  ahmedxgouda marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
| ] | ||
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| # Generated by Django 5.2.1 on 2025-06-15 03:46 | ||
|  | ||
| from django.db import migrations | ||
|  | ||
|  | ||
| class Migration(migrations.Migration): | ||
| dependencies = [ | ||
| ("owasp", "0042_alter_projecthealthmetrics_score"), | ||
| ("owasp", "0043_remove_projecthealthmetrics_has_recent_commits_and_more"), | ||
| ] | ||
|  | ||
| operations = [] | 
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| # Generated by Django 5.2.1 on 2025-06-17 05:51 | ||
|  | ||
| from django.db import migrations | ||
|  | ||
|  | ||
| class Migration(migrations.Migration): | ||
| dependencies = [ | ||
| ("owasp", "0044_merge_20250615_0346"), | ||
| ] | ||
|  | ||
| operations = [ | ||
| migrations.RemoveField( | ||
| model_name="projecthealthmetrics", | ||
| name="has_long_open_issues", | ||
| ), | ||
| migrations.RemoveField( | ||
| model_name="projecthealthmetrics", | ||
| name="has_long_unanswered_issues", | ||
| ), | ||
| migrations.RemoveField( | ||
| model_name="projecthealthmetrics", | ||
| name="has_long_unassigned_issues", | ||
| ), | ||
| migrations.RemoveField( | ||
| model_name="projecthealthmetrics", | ||
| name="has_no_recent_commits", | ||
| ), | ||
| ] | 
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,69 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Test cases for ProjectHealthMetricsNode.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import pytest | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from apps.owasp.graphql.nodes.project_health_metrics import ProjectHealthMetricsNode | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class TestProjectHealthMetricsNode: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def test_project_health_metrics_node_inheritance(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert hasattr(ProjectHealthMetricsNode, "__strawberry_definition__") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def test_meta_configuration(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| field_names = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| field.name for field in ProjectHealthMetricsNode.__strawberry_definition__.fields | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expected_field_names = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "age_days", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "contributors_count", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "forks_count", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "is_funding_requirements_compliant", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "is_leader_requirements_compliant", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "last_commit_days", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "last_pull_request_days", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "last_release_days", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "open_issues_count", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "open_pull_requests_count", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "owasp_page_last_update_days", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "recent_releases_count", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "score", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "stars_count", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "unanswered_issues_count", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "unassigned_issues_count", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert expected_field_names.issubset(field_names) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 
      Comment on lines
    
      +12
     to 
      +34
    
   There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Keep assertion & parametrised lists in sync –  
 @@
             "score",
@@
         assert expected_field_names.issubset(field_names)
 🧰 Tools🪛 Pylint (3.3.7)[error] 14-14: Class 'ProjectHealthMetricsNode' has no 'strawberry_definition' member (E1101) 🤖 Prompt for AI Agents | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _get_field_by_name(self, name): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return next( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| f | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for f in ProjectHealthMetricsNode.__strawberry_definition__.fields | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if f.name == name | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ("field_name", "expected_type"), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ("age_days", int), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ("contributors_count", int), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ("forks_count", int), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ("is_funding_requirements_compliant", bool), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ("is_leader_requirements_compliant", bool), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ("last_commit_days", int), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ("last_pull_request_days", int), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ("last_release_days", int), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ("open_issues_count", int), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ("open_pull_requests_count", int), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ("owasp_page_last_update_days", int), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ("stars_count", int), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ("recent_releases_count", int), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ("unanswered_issues_count", int), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ("unassigned_issues_count", int), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 
      Comment on lines
    
      +46
     to 
      +65
    
   There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add missing entry and drop duplication risk See earlier note – add  @@
             ("stars_count", int),
             ("recent_releases_count", int),
+            ("score", int),  # or float, depending on implementation
             ("unanswered_issues_count", int),
             ("unassigned_issues_count", int),📝 Committable suggestion
 
        Suggested change
       
 🤖 Prompt for AI AgentsThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Score could be None, so it is not possible to include within this test because the test checks if it is not None. What do you think? @arkid15r There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 
 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def test_field_types(self, field_name, expected_type): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| field = self._get_field_by_name(field_name) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert field is not None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert field.type is expected_type | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 
      Comment on lines
    
      +66
     to 
      +69
    
   There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 
 
 -        assert field.type is expected_type
+        from typing import get_origin
+
+        actual = get_origin(field.type) or field.type
+        assert actual is expected_type📝 Committable suggestion
 
        Suggested change
       
 🤖 Prompt for AI Agents | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.
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.
🛠️ Refactor suggestion
Infinite recursion in all resolver methods
Each resolver returns the same attribute name it is defining, causing a self-referential lookup that yields the bound method instead of the computed value and eventually raises
RecursionErrorat runtime.Apply the same pattern (or
self._root.<prop>) tolast_commit_days,last_pull_request_days,last_release_days, andowasp_page_last_update_days.Alternatively, expose these fields via
fields=[…]and drop custom resolvers entirely.🤖 Prompt for AI Agents
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.
@ahmedxgouda have you checked if this suggestion is valid?
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.
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.
No, it is not. Even strawberry type does not have this attribute.