Skip to content

[OSDEV-2185] [Climate TRACE] Display Source By Text#792

Merged
roman-stolar merged 24 commits intomainfrom
OSDEV-2185-display-source-by
Nov 12, 2025
Merged

[OSDEV-2185] [Climate TRACE] Display Source By Text#792
roman-stolar merged 24 commits intomainfrom
OSDEV-2185-display-source-by

Conversation

@roman-stolar
Copy link
Contributor

@roman-stolar roman-stolar commented Nov 4, 2025

OSDEV-2185 [Climate TRACE] Display Source By Text

  • Enhanced partner field display on production location profiles by adding a source_by field to the PartnerField model. This allows administrators to provide rich text descriptions of data sources. The source information is displayed on the facility details page below each partner field value, supporting HTML formatting for links, emphasis, and lists.
  • Updated the facility index serializer to include source_by in the partner fields response only when the field contains content.
  • 0184_add_source_by_to_partner_field.py adds a source_by RichTextField to the PartnerField model, allowing administrators to document the data source for each partner field using rich text formatting (bold, italic, links, lists). The field is optional and uses CKEditor for content editing.
Screenshot 2025-11-05 at 11 59 45

@roman-stolar roman-stolar self-assigned this Nov 4, 2025
@roman-stolar roman-stolar temporarily deployed to Quality Environment November 4, 2025 13:16 — with GitHub Actions Inactive
@roman-stolar roman-stolar temporarily deployed to Quality Environment November 4, 2025 13:16 — with GitHub Actions Inactive
@roman-stolar roman-stolar temporarily deployed to Quality Environment November 4, 2025 13:16 — with GitHub Actions Inactive
@roman-stolar roman-stolar temporarily deployed to Quality Environment November 4, 2025 13:16 — with GitHub Actions Inactive
@roman-stolar roman-stolar temporarily deployed to Quality Environment November 4, 2025 13:16 — with GitHub Actions Inactive
@roman-stolar roman-stolar temporarily deployed to Quality Environment November 4, 2025 13:16 — with GitHub Actions Inactive
@roman-stolar roman-stolar temporarily deployed to Quality Environment November 4, 2025 13:16 — with GitHub Actions Inactive
@roman-stolar roman-stolar temporarily deployed to Quality Environment November 4, 2025 13:16 — with GitHub Actions Inactive
@roman-stolar roman-stolar temporarily deployed to Quality Environment November 4, 2025 13:16 — with GitHub Actions Inactive
@roman-stolar roman-stolar temporarily deployed to Quality Environment November 4, 2025 13:16 — with GitHub Actions Inactive
@roman-stolar roman-stolar temporarily deployed to Quality Environment November 4, 2025 13:16 — with GitHub Actions Inactive
@roman-stolar roman-stolar temporarily deployed to Quality Environment November 4, 2025 13:16 — with GitHub Actions Inactive
@roman-stolar roman-stolar temporarily deployed to Quality Environment November 4, 2025 13:16 — with GitHub Actions Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/django/oar/settings.py (1)

531-535: Environment key required in all envs will break local/tests.

Gate GOOGLE_SERVER_SIDE_API_KEY behind not DEBUG (and/or BATCH_MODE), mirroring other settings.

-GOOGLE_SERVER_SIDE_API_KEY = os.getenv('GOOGLE_SERVER_SIDE_API_KEY')
-if GOOGLE_SERVER_SIDE_API_KEY is None:
-    raise ImproperlyConfigured(
-        'Invalid GOOGLE_SERVER_SIDE_API_KEY provided, must be set')
+GOOGLE_SERVER_SIDE_API_KEY = os.getenv('GOOGLE_SERVER_SIDE_API_KEY')
+if GOOGLE_SERVER_SIDE_API_KEY is None and not DEBUG:
+    raise ImproperlyConfigured(
+        'Invalid GOOGLE_SERVER_SIDE_API_KEY provided, must be set')
🧹 Nitpick comments (5)
src/react/src/util/util.js (1)

1779-1801: source_by → sourceBy mapping is correct; normalize whitespace.

Trim and nullify whitespace-only values to avoid rendering empty containers.

 export const formatExtendedField = ({
   value,
   field_name,
   created_at,
-  source_by,
+  source_by,
   contributor_name,
   is_from_claim,
   is_verified,
   formatValue = rawValue => rawValue,
 }) => {
   const primary = renderUniqueListItems(formatValue(value), field_name);
   const secondary = formatAttribution(created_at, contributor_name);

   return {
     primary,
     secondary,
-    sourceBy: source_by,
+    sourceBy: (typeof source_by === 'string' && source_by.trim()) || null,
     embeddedSecondary: formatAttribution(created_at),
     isVerified: is_verified,
     isFromClaim: is_from_claim,
     key: uuidv4(),
   };
 };
doc/release/RELEASE-NOTES.md (1)

6-22: Tidy heading levels and list indentation; confirm release date.

  • Use “### Migrations” instead of “####” to avoid level jumps; adjust bullets to standard 2‑space indent.
  • Release date (November 15, 2025) is in the future; keep as placeholder but remember to finalize at freeze.

Based on learnings

src/django/oar/settings.py (1)

505-521: CKEditor config: reduce attack surface and align with bleach.

  • Consider removing “Source” button unless strictly needed.
  • Align allowed tags/attrs with BLEACH_*; if links open in new tab, allow and set rel="noopener noreferrer".
  • Add BLEACH_ALLOWED_PROTOCOLS to restrict href schemes.
 CKEDITOR_CONFIGS = {
   'default': {
-    'toolbar': [
+    'toolbar': [
       ['Bold', 'Italic', 'Underline', 'Strike'],
       ['NumberedList', 'BulletedList'],
-      ['Link', 'Unlink'],
-      ['RemoveFormat', 'Source'],
+      ['Link', 'Unlink'],
+      ['RemoveFormat'],
     ],
   }
 }
+
+# Bleach protocol hardening
+BLEACH_ALLOWED_PROTOCOLS = ['http', 'https', 'mailto']
+# If you want to enforce rel on links, also allow it:
+BLEACH_ALLOWED_ATTRIBUTES = {
+    'a': ['href', 'target', 'title', 'rel'],
+}
src/react/src/components/FacilityDetailsItem.jsx (1)

59-67: LGTM on sourceBy propagation; drop unused props to child.

FacilityDetailsDetail doesn’t consume lat/lng; remove to reduce noise.

-            <FacilityDetailsDetail
-                primary={primary}
-                lat={lat}
-                lng={lng}
+            <FacilityDetailsDetail
+                primary={primary}
                 locationLabeled={locationLabeled}
                 secondary={!embed ? secondary : null}
                 sourceBy={!embed ? sourceBy : null}
                 isVerified={isVerified}
                 isFromClaim={isFromClaim}
             />
 ...
-                        <FacilityDetailsDetail
-                            primary={primary || `${lat}, ${lng}` || null}
+                        <FacilityDetailsDetail
+                            primary={primary || `${lat}, ${lng}` || null}
                             secondary={!embed ? secondary : null}
                             sourceBy={!embed ? sourceBy : null}
                             isVerified={isVerified}
                             isFromClaim={isFromClaim}
                         />

Also applies to: 94-101

src/django/api/admin.py (1)

246-255: Consider letting Django auto-generate the widget.

The source_by field is explicitly defined with CKEditorWidget, but since the model field is already a RichTextField from ckeditor.fields, Django's ModelForm should automatically provide the appropriate CKEditor widget. The explicit definition may be unnecessary unless you need specific customization.

If no custom configuration is needed, you can simplify to:

 class PartnerFieldAdminForm(forms.ModelForm):
-    source_by = forms.CharField(
-        required=False,
-        widget=CKEditorWidget()
-    )
-
     class Meta:
         model = PartnerField
         fields = ['name', 'type', 'unit', 'label', 'source_by']

Alternatively, if the explicit widget is intentional for customization, this is fine as-is.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c721dfe and 4b7da95.

📒 Files selected for processing (14)
  • doc/release/RELEASE-NOTES.md (1 hunks)
  • src/django/api/admin.py (3 hunks)
  • src/django/api/constants.py (1 hunks)
  • src/django/api/migrations/0184_add_source_by_to_partner_field.py (1 hunks)
  • src/django/api/models/partner_field.py (3 hunks)
  • src/django/api/serializers/facility/facility_index_extended_field_list_serializer.py (2 hunks)
  • src/django/api/serializers/facility/facility_index_serializer.py (5 hunks)
  • src/django/api/tests/test_facility_index_serializer.py (1 hunks)
  • src/django/oar/settings.py (2 hunks)
  • src/django/requirements.txt (1 hunks)
  • src/react/src/__tests__/components/FacilityDetailsDetail.test.js (1 hunks)
  • src/react/src/components/FacilityDetailsDetail.jsx (3 hunks)
  • src/react/src/components/FacilityDetailsItem.jsx (3 hunks)
  • src/react/src/util/util.js (2 hunks)
🧰 Additional context used
🧠 Learnings (9)
📚 Learning: 2024-12-10T07:09:35.641Z
Learnt from: VadimKovalenkoSNF
Repo: opensupplyhub/open-supply-hub PR: 436
File: src/react/src/components/Contribute/ProductionLocationDialog.jsx:33-181
Timestamp: 2024-12-10T07:09:35.641Z
Learning: In the `ProductionLocationDialog.jsx` component, hardcoded facility data is acceptable for now as per the project's requirements.

Applied to files:

  • src/react/src/components/FacilityDetailsDetail.jsx
📚 Learning: 2024-12-05T10:43:59.922Z
Learnt from: Innavin369
Repo: opensupplyhub/open-supply-hub PR: 437
File: src/react/src/components/Filters/StyledSelect.jsx:31-39
Timestamp: 2024-12-05T10:43:59.922Z
Learning: In the `StyledSelect` component (`src/react/src/components/Filters/StyledSelect.jsx`), the `label` prop is optional. The `InputLabel` should be conditionally rendered only when a label is provided to prevent unnecessary DOM elements, extra padding, and browser console warnings.

Applied to files:

  • src/react/src/components/FacilityDetailsDetail.jsx
📚 Learning: 2025-01-17T16:12:18.285Z
Learnt from: Innavin369
Repo: opensupplyhub/open-supply-hub PR: 483
File: src/react/src/__tests__/components/SearchByNameAndAddressSuccessResult.test.js:0-0
Timestamp: 2025-01-17T16:12:18.285Z
Learning: In the SearchByNameAndAddressSuccessResult component's tests, attempting to test internal navigation logic through mocking useHistory is not feasible, and button presence/click events should be covered in the main rendering test case.

Applied to files:

  • src/react/src/__tests__/components/FacilityDetailsDetail.test.js
📚 Learning: 2024-12-03T10:29:02.409Z
Learnt from: VadimKovalenkoSNF
Repo: opensupplyhub/open-supply-hub PR: 436
File: src/react/src/components/DialogTooltip.jsx:0-0
Timestamp: 2024-12-03T10:29:02.409Z
Learning: In the `DialogTooltip` component (`src/react/src/components/DialogTooltip.jsx`), there are existing tooltip tests that cover common cases, and further detailed tests are not necessary.

Applied to files:

  • src/react/src/__tests__/components/FacilityDetailsDetail.test.js
📚 Learning: 2025-01-27T07:20:43.334Z
Learnt from: VadimKovalenkoSNF
Repo: opensupplyhub/open-supply-hub PR: 470
File: src/react/src/__tests__/components/ProductionLocationDialog.test.js:0-0
Timestamp: 2025-01-27T07:20:43.334Z
Learning: In the Open Supply Hub project, buttons wrapped by the `DialogTooltip` component control their disabled state through CSS pointer-events rather than the HTML disabled attribute, requiring tests to check `getComputedStyle().pointerEvents` instead of using `toBeDisabled()`.

Applied to files:

  • src/react/src/__tests__/components/FacilityDetailsDetail.test.js
📚 Learning: 2024-12-10T15:07:54.819Z
Learnt from: roman-stolar
Repo: opensupplyhub/open-supply-hub PR: 448
File: src/react/src/util/util.js:1317-1317
Timestamp: 2024-12-10T15:07:54.819Z
Learning: Changes to date handling functions like `formatDate` in `src/react/src/util/util.js` should be made carefully, considering that different parts of the codebase may intentionally use local time instead of UTC, and broad changes to `moment()` usage may not be appropriate without context.

Applied to files:

  • src/react/src/util/util.js
📚 Learning: 2025-06-02T13:24:57.659Z
Learnt from: VadimKovalenkoSNF
Repo: opensupplyhub/open-supply-hub PR: 641
File: doc/release/RELEASE-NOTES.md:6-35
Timestamp: 2025-06-02T13:24:57.659Z
Learning: The Open Supply Hub team keeps placeholders in release notes until code freeze, then fills in the actual content once all changes are finalized for the release.

Applied to files:

  • doc/release/RELEASE-NOTES.md
📚 Learning: 2024-11-28T11:29:28.139Z
Learnt from: Innavin369
Repo: opensupplyhub/open-supply-hub PR: 431
File: src/django/api/serializers/v1/opensearch_common_validators/countries_validator.py:23-24
Timestamp: 2024-11-28T11:29:28.139Z
Learning: The files `src/django/api/tests/test_facility_claim_view_set.py`, `src/django/api/views/facility/facilities_view_set.py`, and `src/django/api/facility_actions/processing_facility_api.py` are not part of the V1 codebase in the Open Supply Hub project.

Applied to files:

  • src/django/api/tests/test_facility_index_serializer.py
📚 Learning: 2024-11-28T11:40:54.281Z
Learnt from: Innavin369
Repo: opensupplyhub/open-supply-hub PR: 431
File: src/django/api/tests/test_production_locations_view_set.py:45-46
Timestamp: 2024-11-28T11:40:54.281Z
Learning: Files `src/django/api/views/facility/facilities_view_set.py`, `src/django/api/facility_actions/processing_facility_api.py`, and `src/django/api/tests/test_facility_claim_view_set.py` are not part of v1 and should not be considered in v1-related code reviews.

Applied to files:

  • src/django/api/tests/test_facility_index_serializer.py
🧬 Code graph analysis (4)
src/react/src/__tests__/components/FacilityDetailsDetail.test.js (1)
src/react/src/components/FacilityDetailsDetail.jsx (1)
  • FacilityDetailsDetail (52-94)
src/django/api/tests/test_facility_index_serializer.py (3)
src/django/api/models/partner_field.py (1)
  • save (69-71)
src/django/api/models/extended_field.py (1)
  • ExtendedField (7-111)
src/django/api/serializers/facility/facility_index_serializer.py (1)
  • FacilityIndexSerializer (47-566)
src/django/api/admin.py (1)
src/django/api/models/partner_field.py (2)
  • Meta (24-25)
  • PartnerField (8-76)
src/django/api/serializers/facility/facility_index_serializer.py (1)
src/django/api/models/partner_field.py (1)
  • PartnerField (8-76)
🪛 Biome (2.1.2)
src/react/src/components/FacilityDetailsDetail.jsx

[error] 84-84: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

🪛 markdownlint-cli2 (0.18.1)
doc/release/RELEASE-NOTES.md

12-12: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4

(MD001, heading-increment)


20-20: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


21-21: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)

🪛 Ruff (0.14.3)
src/django/api/migrations/0184_add_source_by_to_partner_field.py

9-11: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


13-19: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

src/django/api/admin.py

254-254: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: run-integration-test-code-quality
  • GitHub Check: get-base-branch-countries-cov
  • GitHub Check: get-base-branch-dd-cov
  • GitHub Check: run-contricleaner-code-quality
  • GitHub Check: run-django-code-quality
  • GitHub Check: run-flake8-linter
  • GitHub Check: run-countries-code-quality
  • GitHub Check: run-dd-code-quality
  • GitHub Check: get-base-branch-contricleaner-cov
  • GitHub Check: get-base-branch-django-cov
🔇 Additional comments (14)
src/django/requirements.txt (1)

21-21: CKEditor dependency addition looks good.

Version pin is reasonable for Django 3.2; INSTALLED_APPS and config are present in settings.

src/react/src/__tests__/components/FacilityDetailsDetail.test.js (1)

50-123: Good coverage for sourceBy HTML rendering and null/empty cases.

Once sanitization is implemented, add one test that verifies unsafe tags/attrs are stripped.

src/django/api/constants.py (1)

302-302: Cache key rename verified—no legacy references remain.

The old PARTNER_FIELD_NAMES_LIST_KEY constant has been completely removed from the codebase. The new PARTNER_FIELD_LIST_KEY is properly defined and actively used in cache operations across facility_index_serializer.py and partner_field.py. No stale cache keys found.

src/django/api/tests/test_facility_index_serializer.py (2)

140-180: LGTM! Comprehensive test for source_by propagation.

The test properly validates that HTML content in the source_by field is correctly serialized and propagated through the facility index serializer.


182-219: LGTM! Good coverage for null case.

The test correctly verifies that source_by is serialized as None when not set on the partner field.

src/django/api/admin.py (1)

258-260: LGTM! Appropriate admin configuration.

The admin updates correctly integrate the new source_by field into the list display and search functionality.

src/django/api/migrations/0184_add_source_by_to_partner_field.py (1)

1-20: LGTM! Standard Django migration.

The migration correctly adds the source_by RichTextField to the PartnerField model with appropriate nullable and blank attributes.

src/django/api/models/partner_field.py (2)

53-61: LGTM! Well-defined RichTextField.

The source_by field is properly configured with appropriate constraints and clear help text.


71-75: LGTM! Cache invalidation properly updated.

The cache key change from PARTNER_FIELD_NAMES_LIST_KEY to PARTNER_FIELD_LIST_KEY correctly reflects the shift to caching full PartnerField objects.

src/django/api/serializers/facility/facility_index_extended_field_list_serializer.py (2)

21-21: LGTM! Field added to serialization.

The source_by field is correctly added to the serializer's field list.


53-56: LGTM! Proper context-based serialization.

The logic correctly retrieves source_by from the serialization context when present, with appropriate null checking and fallback behavior.

src/django/api/serializers/facility/facility_index_serializer.py (3)

95-136: LGTM! Well-implemented shift to PartnerField objects.

The method correctly processes full PartnerField objects, extracting both field_name and source_by, and properly passes source_by through the serializer context. Error handling and sorting logic remain intact.


196-208: LGTM! Appropriate caching of PartnerField objects.

The method correctly caches full PartnerField objects instead of just names, enabling access to the source_by field. The list() call properly forces queryset evaluation before caching.


435-444: LGTM! Consistent method usage.

The calls to the renamed method and parameter passing are correct and consistent with the refactored implementation.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/django/api/admin.py (2)

247-256: Consider using Meta.widgets instead of redefining the field.

Since source_by is already a RichTextField in the model, explicitly defining it as a CharField with CKEditorWidget is redundant. The cleaner Django pattern is to use the widgets attribute in Meta:

 class PartnerFieldAdminForm(forms.ModelForm):
-    source_by = forms.CharField(
-        required=False,
-        widget=CKEditorWidget()
-    )
-
     class Meta:
         model = PartnerField
         fields = ['name', 'type', 'unit', 'label', 'source_by']
+        widgets = {
+            'source_by': CKEditorWidget()
+        }

This approach avoids duplicating field configuration and lets Django handle the field type automatically from the model.


261-261: Searching HTML content includes markup tags.

Adding source_by to search_fields will search the raw HTML content, including tags like <p>, <a>, etc. While functional, this might match unintended results when users search for terms that appear in HTML markup.

This is acceptable for most use cases, but if cleaner search behavior is desired, you could create a plain-text version of the field for searching or exclude it from search_fields if the HTML noise outweighs the benefit.

doc/release/RELEASE-NOTES.md (1)

20-21: Align list indentation with linter standards.

Lines 20–21 use 4-space indentation for nested list items, but the Markdown linter expects 2 spaces (MD007). While this matches the existing pattern throughout the file (e.g., Release 2.15.0), standardizing to 2 spaces improves consistency with Markdown conventions.

~### Release instructions
~* Ensure that the following commands are included in the `post_deployment` command:
~
~  * `migrate`
~
~  * `reindex_database`
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4b7da95 and 794ff67.

📒 Files selected for processing (3)
  • doc/release/RELEASE-NOTES.md (1 hunks)
  • src/django/api/admin.py (3 hunks)
  • src/django/api/tests/test_facility_index_serializer.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/django/api/tests/test_facility_index_serializer.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-02T13:24:57.659Z
Learnt from: VadimKovalenkoSNF
Repo: opensupplyhub/open-supply-hub PR: 641
File: doc/release/RELEASE-NOTES.md:6-35
Timestamp: 2025-06-02T13:24:57.659Z
Learning: The Open Supply Hub team keeps placeholders in release notes until code freeze, then fills in the actual content once all changes are finalized for the release.

Applied to files:

  • doc/release/RELEASE-NOTES.md
🧬 Code graph analysis (1)
src/django/api/admin.py (1)
src/django/api/models/partner_field.py (2)
  • Meta (24-25)
  • PartnerField (8-76)
🪛 markdownlint-cli2 (0.18.1)
doc/release/RELEASE-NOTES.md

12-12: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4

(MD001, heading-increment)


20-20: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


21-21: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)

🪛 Ruff (0.14.3)
src/django/api/admin.py

254-254: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

🔇 Additional comments (3)
src/django/api/admin.py (2)

3-3: LGTM! Imports are appropriate.

The new imports for forms and CKEditorWidget are correctly added to support the admin form customization.

Also applies to: 19-19


260-260: Verify HTML rendering in the admin list view.

Adding source_by (which contains rich text/HTML) directly to list_display may display raw HTML tags in the admin list, affecting readability.

Please verify how the HTML content renders in the admin list view. If raw tags are displayed, consider using a custom method to show sanitized or truncated plain text:

def source_by_preview(self, obj):
    from django.utils.html import strip_tags
    if obj.source_by:
        plain = strip_tags(obj.source_by)
        return plain[:50] + '...' if len(plain) > 50 else plain
    return '-'

source_by_preview.short_description = 'Source By'

list_display = ('name', 'type', 'label', 'unit', 'source_by_preview', 'created_at')
doc/release/RELEASE-NOTES.md (1)

13-16: Content is accurate and aligns with PR objectives.

The migration and feature descriptions for source_by RichTextField in PartnerField correctly capture the key changes: optional field with CKEditor support, rich formatting (bold, italic, links, lists), and integration with facility index serializers and views. The release notes are ready for deployment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 794ff67 and 87c0400.

📒 Files selected for processing (1)
  • doc/release/RELEASE-NOTES.md (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-02T13:24:57.659Z
Learnt from: VadimKovalenkoSNF
Repo: opensupplyhub/open-supply-hub PR: 641
File: doc/release/RELEASE-NOTES.md:6-35
Timestamp: 2025-06-02T13:24:57.659Z
Learning: The Open Supply Hub team keeps placeholders in release notes until code freeze, then fills in the actual content once all changes are finalized for the release.

Applied to files:

  • doc/release/RELEASE-NOTES.md
🪛 markdownlint-cli2 (0.18.1)
doc/release/RELEASE-NOTES.md

22-22: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


23-23: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: run-eslint-linter-and-prettier-formatter
  • GitHub Check: run-countries-code-quality
  • GitHub Check: get-base-branch-django-cov
  • GitHub Check: run-flake8-linter
  • GitHub Check: run-dd-code-quality
  • GitHub Check: get-base-branch-contricleaner-cov
  • GitHub Check: run-integration-test-code-quality
  • GitHub Check: get-base-branch-dd-cov
  • GitHub Check: run-django-code-quality
  • GitHub Check: run-contricleaner-code-quality
  • GitHub Check: get-base-branch-countries-cov
  • GitHub Check: run-fe-code-quality
  • GitHub Check: get-base-branch-fe-cov
🔇 Additional comments (1)
doc/release/RELEASE-NOTES.md (1)

6-23: Release notes for 2.16.0 are well-structured and comprehensive.

The release notes follow the established pattern from Release 2.15.0 and earlier. The heading hierarchy is correct, the migration is clearly documented, the "What's new" section provides adequate detail, and release instructions are appropriate. The content appears finalized (not placeholder text), which aligns well with the PR objectives for adding the source_by field feature.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/django/api/serializers/facility/facility_index_serializer.py (1)

202-204: Consider optimizing the database query for better performance.

The query now fetches all fields of PartnerField objects, including the potentially large source_by RichTextField. However, based on the code in __serialize_and_sort_partner_fields (lines 105-107), only field.name and field.source_by are actually used in this code path.

If unit and label fields are not needed here, consider optimizing the query:

-        partner_fields = list(
-            PartnerField.objects.all()
-        )
+        partner_fields = list(
+            PartnerField.objects.only('name', 'source_by')
+        )

This would reduce memory usage and improve cache efficiency, especially if there are many partner fields or if source_by contains substantial HTML content.

Note: If unit and label are needed elsewhere in the serialization flow, this optimization may not be applicable.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 26abcc5 and 04fff34.

📒 Files selected for processing (2)
  • doc/release/RELEASE-NOTES.md (2 hunks)
  • src/django/api/serializers/facility/facility_index_serializer.py (5 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-02T13:24:57.659Z
Learnt from: VadimKovalenkoSNF
Repo: opensupplyhub/open-supply-hub PR: 641
File: doc/release/RELEASE-NOTES.md:6-35
Timestamp: 2025-06-02T13:24:57.659Z
Learning: The Open Supply Hub team keeps placeholders in release notes until code freeze, then fills in the actual content once all changes are finalized for the release.

Applied to files:

  • doc/release/RELEASE-NOTES.md
🧬 Code graph analysis (1)
src/django/api/serializers/facility/facility_index_serializer.py (1)
src/django/api/models/partner_field.py (1)
  • PartnerField (8-76)
🪛 markdownlint-cli2 (0.18.1)
doc/release/RELEASE-NOTES.md

23-23: Multiple headings with the same content

(MD024, no-duplicate-heading)


37-37: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


38-38: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: run-eslint-linter-and-prettier-formatter
  • GitHub Check: run-integration-test-code-quality
  • GitHub Check: run-flake8-linter
  • GitHub Check: get-base-branch-contricleaner-cov
  • GitHub Check: get-base-branch-countries-cov
  • GitHub Check: get-base-branch-dd-cov
  • GitHub Check: run-dd-code-quality
  • GitHub Check: run-contricleaner-code-quality
  • GitHub Check: get-base-branch-fe-cov
  • GitHub Check: run-fe-code-quality
  • GitHub Check: run-django-code-quality
  • GitHub Check: get-base-branch-django-cov

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
doc/release/RELEASE-NOTES.md (1)

37-38: Resolve list indentation to comply with project standards.

Lines 37–38 use 4-space indentation for nested list items, but the project's Markdown linter (markdownlint-cli2) expects 2-space indentation (MD007 rule). Although you previously declined this fix, maintaining consistent indentation across all releases improves maintainability. Please apply 2-space indentation:

 ### Release instructions
 * Ensure that the following commands are included in the `post_deployment` command:
-    * `migrate`
-    * `reindex_database`
+  * `migrate`
+  * `reindex_database`
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 04fff34 and 0d483a8.

📒 Files selected for processing (1)
  • doc/release/RELEASE-NOTES.md (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-02T13:24:57.659Z
Learnt from: VadimKovalenkoSNF
Repo: opensupplyhub/open-supply-hub PR: 641
File: doc/release/RELEASE-NOTES.md:6-35
Timestamp: 2025-06-02T13:24:57.659Z
Learning: The Open Supply Hub team keeps placeholders in release notes until code freeze, then fills in the actual content once all changes are finalized for the release.

Applied to files:

  • doc/release/RELEASE-NOTES.md
🪛 markdownlint-cli2 (0.18.1)
doc/release/RELEASE-NOTES.md

23-23: Multiple headings with the same content

(MD024, no-duplicate-heading)


37-37: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


38-38: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)

🔇 Additional comments (1)
doc/release/RELEASE-NOTES.md (1)

40-40: The review comment is incorrect. The file already has the required spacing.

The current file contains two blank lines (lines 39-40) between the Release 2.15.1 section and the Release 2.15.0 section, which matches the stated project convention. No additional blank line is needed.

Likely an incorrect or invalid review comment.

Copy link
Contributor

@VadimKovalenkoSNF VadimKovalenkoSNF left a comment

Choose a reason for hiding this comment

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

Approved.
P.S. - I would rather change source_by to something like contributed_by since we consistently use the term source across the system to refer to APIs or list uploads.

… in Production Location Page (#807)

**[OSDEV-2199](https://opensupplyhub.atlassian.net/browse/OSDEV-2199)
[Climate TRACE] Display partner field "label" and "unit" in Production
Location Page**

- Added `unit` and `label` metadata from `PartnerField` to the
serialized partner fields payload.
- Production Location detail pages now render the `unit` inline with
field values and display custom partner field `label`.

<img width="806" height="232" alt="Screenshot 2025-11-11 at 11 13 17"
src="https://github.com/user-attachments/assets/8f6c1e3e-80d8-4e1d-bc47-ae38c58975cd"
/>

_**Depend on**_ ->
#792

[OSDEV-2199]:
https://opensupplyhub.atlassian.net/browse/OSDEV-2199?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
@sonarqubecloud
Copy link

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/react/src/components/FacilityDetailsDetail.jsx (1)

90-96: Unsafe HTML injection: confirm sanitization path

Rendering sourceBy via dangerouslySetInnerHTML is an XSS sink. If you already sanitize on the server (e.g., bleach allow‑list), we're good; otherwise sanitize client‑side (DOMPurify) or tighten allowed tags/attrs.

If server-side: please point to the sanitizer. If not, consider this minimal FE change:

+import DOMPurify from 'dompurify';
 ...
-                    dangerouslySetInnerHTML={{ __html: sourceBy }}
+                    dangerouslySetInnerHTML={{
+                      __html: DOMPurify.sanitize(sourceBy, {
+                        ALLOWED_TAGS: ['a','strong','em','ul','ol','li','br','p','code'],
+                        ALLOWED_ATTR: ['href','title','target','rel'],
+                      }),
+                    }}
🧹 Nitpick comments (1)
src/django/api/serializers/facility/facility_index_serializer.py (1)

200-213: Avoid caching ORM instances; cache plain dicts instead

Caching PartnerField model instances increases memory/serialization overhead and can be brittle across deploys/workers. Cache only the fields you need.

-    def __get_cached_partner_fields():
-        cached_names = cache.get(PARTNER_FIELD_LIST_KEY)
+    def __get_cached_partner_fields():
+        cached = cache.get(PARTNER_FIELD_LIST_KEY)
-        if cached_names is not None:
-            return cached_names
+        if cached is not None:
+            return cached
-        partner_fields = list(
-            PartnerField.objects.all()
-        )
-        cache.set(PARTNER_FIELD_LIST_KEY, partner_fields, 60)
-        return partner_fields
+        partner_fields = list(
+            PartnerField.objects.values('name', 'source_by', 'unit', 'label')
+        )
+        cache.set(PARTNER_FIELD_LIST_KEY, partner_fields, 60)
+        return partner_fields

And adjust usage in __serialize_and_sort_partner_fields:

-        for field in partner_fields:
-            field_name = field.name
-            source_by = field.source_by
-            unit = field.unit
-            label = field.label
+        for field in partner_fields:
+            field_name = field['name']
+            source_by = field['source_by']
+            unit = field['unit']
+            label = field['label']
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0d483a8 and d1ce0a8.

📒 Files selected for processing (11)
  • doc/release/RELEASE-NOTES.md (2 hunks)
  • src/django/api/serializers/facility/facility_index_extended_field_list_serializer.py (2 hunks)
  • src/django/api/serializers/facility/facility_index_serializer.py (5 hunks)
  • src/django/api/tests/test_facility_index_serializer.py (1 hunks)
  • src/react/src/__tests__/components/FacilityDetailsDetail.test.js (1 hunks)
  • src/react/src/__tests__/components/FacilityDetailsGeneralFields.test.js (1 hunks)
  • src/react/src/components/FacilityDetailsDetail.jsx (3 hunks)
  • src/react/src/components/FacilityDetailsGeneralFields.jsx (2 hunks)
  • src/react/src/components/FacilityDetailsItem.jsx (3 hunks)
  • src/react/src/components/FacilityDetailsLocationFields.jsx (1 hunks)
  • src/react/src/util/util.js (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/react/src/components/FacilityDetailsLocationFields.jsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/react/src/util/util.js
  • src/django/api/tests/test_facility_index_serializer.py
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2024-12-10T07:09:35.641Z
Learnt from: VadimKovalenkoSNF
Repo: opensupplyhub/open-supply-hub PR: 436
File: src/react/src/components/Contribute/ProductionLocationDialog.jsx:33-181
Timestamp: 2024-12-10T07:09:35.641Z
Learning: In the `ProductionLocationDialog.jsx` component, hardcoded facility data is acceptable for now as per the project's requirements.

Applied to files:

  • src/react/src/components/FacilityDetailsItem.jsx
  • src/react/src/components/FacilityDetailsDetail.jsx
📚 Learning: 2025-06-02T13:24:57.659Z
Learnt from: VadimKovalenkoSNF
Repo: opensupplyhub/open-supply-hub PR: 641
File: doc/release/RELEASE-NOTES.md:6-35
Timestamp: 2025-06-02T13:24:57.659Z
Learning: The Open Supply Hub team keeps placeholders in release notes until code freeze, then fills in the actual content once all changes are finalized for the release.

Applied to files:

  • doc/release/RELEASE-NOTES.md
📚 Learning: 2025-01-17T16:12:18.285Z
Learnt from: Innavin369
Repo: opensupplyhub/open-supply-hub PR: 483
File: src/react/src/__tests__/components/SearchByNameAndAddressSuccessResult.test.js:0-0
Timestamp: 2025-01-17T16:12:18.285Z
Learning: In the SearchByNameAndAddressSuccessResult component's tests, attempting to test internal navigation logic through mocking useHistory is not feasible, and button presence/click events should be covered in the main rendering test case.

Applied to files:

  • src/react/src/__tests__/components/FacilityDetailsDetail.test.js
  • src/react/src/__tests__/components/FacilityDetailsGeneralFields.test.js
📚 Learning: 2024-12-03T10:29:02.409Z
Learnt from: VadimKovalenkoSNF
Repo: opensupplyhub/open-supply-hub PR: 436
File: src/react/src/components/DialogTooltip.jsx:0-0
Timestamp: 2024-12-03T10:29:02.409Z
Learning: In the `DialogTooltip` component (`src/react/src/components/DialogTooltip.jsx`), there are existing tooltip tests that cover common cases, and further detailed tests are not necessary.

Applied to files:

  • src/react/src/__tests__/components/FacilityDetailsDetail.test.js
📚 Learning: 2024-12-05T10:43:59.922Z
Learnt from: Innavin369
Repo: opensupplyhub/open-supply-hub PR: 437
File: src/react/src/components/Filters/StyledSelect.jsx:31-39
Timestamp: 2024-12-05T10:43:59.922Z
Learning: In the `StyledSelect` component (`src/react/src/components/Filters/StyledSelect.jsx`), the `label` prop is optional. The `InputLabel` should be conditionally rendered only when a label is provided to prevent unnecessary DOM elements, extra padding, and browser console warnings.

Applied to files:

  • src/react/src/components/FacilityDetailsGeneralFields.jsx
  • src/react/src/components/FacilityDetailsDetail.jsx
📚 Learning: 2024-12-10T15:07:54.819Z
Learnt from: roman-stolar
Repo: opensupplyhub/open-supply-hub PR: 448
File: src/react/src/util/util.js:1317-1317
Timestamp: 2024-12-10T15:07:54.819Z
Learning: Changes to date handling functions like `formatDate` in `src/react/src/util/util.js` should be made carefully, considering that different parts of the codebase may intentionally use local time instead of UTC, and broad changes to `moment()` usage may not be appropriate without context.

Applied to files:

  • src/react/src/components/FacilityDetailsDetail.jsx
📚 Learning: 2024-12-03T06:52:22.170Z
Learnt from: VadimKovalenkoSNF
Repo: opensupplyhub/open-supply-hub PR: 436
File: src/react/src/components/ProductionLocationDialog.jsx:255-257
Timestamp: 2024-12-03T06:52:22.170Z
Learning: In `src/react/src/components/ProductionLocationDialog.jsx`, the 'Submit another Location' button's `onClick` handler logs to the console as the endpoint is not yet implemented on the frontend.

Applied to files:

  • src/react/src/components/FacilityDetailsDetail.jsx
📚 Learning: 2024-12-10T09:00:29.628Z
Learnt from: VadimKovalenkoSNF
Repo: opensupplyhub/open-supply-hub PR: 436
File: src/react/src/util/styles.js:567-571
Timestamp: 2024-12-10T09:00:29.628Z
Learning: In the Open Supply Hub project, when defining font sizes in styles (e.g., in `src/react/src/util/styles.js`), prefer using `px` units directly rather than using `theme.typography.pxToRem()` function, as `px` units are used consistently throughout the project.

Applied to files:

  • src/react/src/components/FacilityDetailsDetail.jsx
🧬 Code graph analysis (2)
src/django/api/serializers/facility/facility_index_serializer.py (1)
src/django/api/models/partner_field.py (1)
  • PartnerField (8-76)
src/react/src/__tests__/components/FacilityDetailsDetail.test.js (1)
src/react/src/components/FacilityDetailsDetail.jsx (1)
  • FacilityDetailsDetail (60-104)
🪛 Biome (2.1.2)
src/react/src/components/FacilityDetailsDetail.jsx

[error] 94-94: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

🪛 markdownlint-cli2 (0.18.1)
doc/release/RELEASE-NOTES.md

23-23: Multiple headings with the same content

(MD024, no-duplicate-heading)


38-38: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


39-39: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)

🔇 Additional comments (6)
src/react/src/__tests__/components/FacilityDetailsDetail.test.js (1)

50-69: Solid coverage for sourceBy and unit rendering

These tests validate HTML rendering, null/empty handling, and unit placement well. Nice work.

Also applies to: 71-90, 91-109, 111-127, 129-149, 151-165

src/react/src/__tests__/components/FacilityDetailsGeneralFields.test.js (1)

315-347: Label fallback behavior well exercised

Both cases (explicit label vs. fallback) are covered and match the intended UI.

Also applies to: 348-379

src/react/src/components/FacilityDetailsGeneralFields.jsx (1)

150-158: Correct partner-field label precedence

Using topValue.label with fallback to generated label is correct and matches the UX.

src/react/src/components/FacilityDetailsItem.jsx (1)

96-104: Confirm repeated source info in drawer

Additional entries also render sourceBy (since {...item} is passed). If the intent is to show source once per field (top only), null it for additionalContent items; otherwise keep as is.

Option to show top only:

-                        <FacilityDetailsDetail
-                            primary={primary || `${lat}, ${lng}` || null}
-                            secondary={!embed ? secondary : null}
-                            sourceBy={!embed ? sourceBy : null}
-                            unit={!embed ? unit : null}
-                            isVerified={isVerified}
-                            isFromClaim={isFromClaim}
-                        />
+                        <FacilityDetailsDetail
+                            primary={primary || `${lat}, ${lng}` || null}
+                            secondary={!embed ? secondary : null}
+                            sourceBy={!embed ? sourceBy : null}
+                            unit={!embed ? unit : null}
+                            isVerified={isVerified}
+                            isFromClaim={isFromClaim}
+                        />
...
-                        {isOpen && additionalContent.map(item => (
+                        {isOpen && additionalContent.map(item => (
                             <div className={classes.itemWrapper} key={item.key}>
-                                <FacilityDetailsDetail {...item} />
+                                <FacilityDetailsDetail {...{ ...item, sourceBy: null, unit: null }} />
                             </div>
                         ))}
doc/release/RELEASE-NOTES.md (1)

21-41: Release 2.15.1 entry reads well; migration number matches code

Migration 0185 reference and feature notes align with the changes in this PR.

Also applies to: 29-35

src/django/api/serializers/facility/facility_index_serializer.py (1)

210-210: Cache TTL cut to 60s — intended?

Previously 600s; confirm the lower TTL is deliberate to reflect frequent admin edits to partner fields. If not, consider reverting to reduce DB hits.

Copy link
Contributor

@vlad-shapik vlad-shapik left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Contributor

@VadimKovalenkoSNF VadimKovalenkoSNF left a comment

Choose a reason for hiding this comment

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

Re-approved.

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.

3 participants