Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save ColinDKelley/55c97a11334ab4fb4aca5af4086da4b6 to your computer and use it in GitHub Desktop.

Select an option

Save ColinDKelley/55c97a11334ab4fb4aca5af4086da4b6 to your computer and use it in GitHub Desktop.
Hidden From Reporting Tag - Implementation Plan
name Hidden From Reporting Tag
overview Add a `$Hidden From Reporting` standard data tag and make all reporting surfaces treat tagged fields as if they were deleted (not present in the dictionary).
todos
id content status
register-tag
Create packs/standard_data/catalog/hidden_from_reporting_tag.rb with TagDefinition registration
pending
id content status
field-scope
Add `visible_in_reporting` scope to CustomerData::Field
in_progress
id content status
dictionary-method
Add `reportable_fields` method to CustomerData::Dictionary
pending
id content status
update-availability
Update MarketingDataAvailability#marketing_data_field_for_type_slot to use reportable_fields
pending
id content status
update-helper
Update MarketingDataHelper#setup_for_dictionary to use reportable_fields
pending
id content status
update-detail
Update DetailReportCustomerData#customer_data_field_for_type_index to use reportable_fields
pending
id content status
update-summary
Update SummaryReportCustomerData#dictionary_fields to use reportable_fields
pending
id content status
tests
Add unit and integration tests for scope, dictionary method, and all four reporting paths
pending
id content status
post-deploy
Create post-deploy script template for tagging specific fields
pending
isProject false

Hidden From Reporting Tag

Architecture Summary

When a CustomerData::Field is hard-deleted, these four independent field-lookup caches stop returning it, which causes all reporting surfaces to exclude the field:

flowchart TD
    subgraph fieldSources [Field Lookup Caches]
        A["MarketingDataAvailability\n#marketing_data_field_for_type_slot\ndict.fields.index_by(&:internal_name)"]
        B["MarketingDataHelper\n#setup_for_dictionary\ndict.fields.index_by(&:internal_name)"]
        C["DetailReportCustomerData\n#customer_data_field_for_type_index\ndict.fields.build_hash"]
        D["SummaryReportCustomerData\n#dictionary_fields\ndict.fields.to_a"]
    end

    subgraph consumers [Reporting Surfaces]
        E[ColumnCatalog / ReportAdapterCatalog\nES + warehouse reports]
        F[Legacy Detail Reports]
        G[Legacy Summary Reports]
        H[GraphQL reportQueryColumns]
    end

    A --> E
    B --> E
    B --> H
    C --> F
    D --> G
Loading

Our goal: make tagged fields disappear from these same four caches without actually deleting them.


Part A: Register the Tag in StandardData

Create a new catalog registration file following the exact pattern of existing tags like $Lead and $Consumer Property.

  • New file: packs/standard_data/catalog/hidden_from_reporting_tag.rb
  • Register one StandardData::TagDefinition:
    • tag_name: '$Hidden From Reporting'
    • data_type: :any (applies to all field types, like $Consumer Property)
    • description: and display_name: as appropriate
  • The CatalogLoader auto-discovers *.rb in packs/standard_data/catalog/, so no wiring needed.

Part B: Exclude Tagged Fields from Reporting

Strategy: Single scope on CustomerData::Field, single method on Dictionary

Add one ActiveRecord scope and one dictionary convenience method, then update the four cache-building call sites.

Step 1: Add scope to CustomerData::Field

In packs/customer_data/app/models/customer_data/field.rb, add:

scope :visible_in_reporting, -> {
  where.not(
    id: joins(:tags).where(customer_data_tags: { name: '$Hidden From Reporting' }).select(:id)
  )
}

This uses a NOT IN subquery on the existing HABTM join, so it needs no new indexes (the join table PK is (field_id, tag_id) and customer_data_tags has a unique index on (dictionary_id, name)).

Step 2: Add reportable_fields to CustomerData::Dictionary

In packs/customer_data/app/models/customer_data/dictionary.rb, add:

def reportable_fields
  fields.visible_in_reporting
end

Step 3: Update the four cache-building sites

Each site changes dict.fields to dict.reportable_fields:

File Method Change
marketing_data_availability.rb marketing_data_field_for_type_slot (line 69) dict.fields.index_by -> dict.reportable_fields.index_by
marketing_data_helper.rb setup_for_dictionary (line 19) marketing_data_dictionary.fields.index_by -> marketing_data_dictionary.reportable_fields.index_by
detail_report_customer_data.rb customer_data_field_for_type_index (line 407) dict.fields.build_hash -> dict.reportable_fields.build_hash
summary_report_customer_data.rb dictionary_fields (line 374) effective_customer_dictionary.fields.to_a -> effective_customer_dictionary.reportable_fields.to_a

These four changes make tagged fields disappear from all downstream reporting surfaces (ColumnCatalog, ReportAdapterCatalog, GraphQL reportQueryColumns, legacy detail/summary reports, ES queries) because every one of those consumers gets its field information from one of these four caches.

What is NOT affected (intentionally)

  • Management UI (ManageCustomerData GraphQL resolvers): still uses dictionary.fields directly -- hidden fields remain visible and editable.
  • LookupFieldCatalog (lookup tables / spreadsheets): uses @dictionary.fields -- lookup tables can still reference hidden fields. (Decide if this should also filter.)
  • Consumer profile import (tagged_as("$Consumer Property")): unrelated tag, unaffected.
  • Data ingest / JS tag / call processing: writes to fields by slot regardless of tags.

Part C: Apply the Tag to Fields (post-deploy script)

Create a post-deploy script to tag specific fields. The mechanism uses the existing Field#standard_data_tag_names= API:

field.standard_data_tag_names = field.standard_data_tag_names.to_a + ['$Hidden From Reporting']

(Which fields to tag is a separate decision -- the script template will be ready.)


Testing Strategy

  • Unit: CustomerData::Field spec for visible_in_reporting scope -- field with/without tag
  • Unit: CustomerData::Dictionary spec for reportable_fields
  • Unit: MarketingDataAvailability spec -- field tagged $Hidden From Reporting returns nil from marketing_data_field_available
  • Unit: MarketingDataHelper spec -- setup_for_dictionary excludes hidden field from @fields_hash
  • Integration: Detail/summary report specs -- hidden field columns are not visible
  • Catalog: StandardData::Catalog spec -- $Hidden From Reporting tag is registered

Decision Points

  1. Should LookupFieldCatalog also exclude hidden fields? Currently it iterates @dictionary.fields for lookup table column pickers. If yes, same one-line change.
  2. Should the management UI indicate a field is hidden from reporting? The GraphQL customerDataFields resolver could expose tag info for UI treatment.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment