Skip to content

Conversation

zendesk-punavwalke
Copy link
Contributor

@zendesk-punavwalke zendesk-punavwalke commented Aug 11, 2025

💐

/cc @zendesk/wattle

Description

Note : Translations are already merged with main.

The PR implements validation limits on Custom Objects V2 requirements in ZAS

Custom Objects V2 requirements = Custom Objects v2 + custom object fields +custom object triggers

Requirement structure with required fields:

{
  "custom_objects_v2": {
    "objects": [
      {
        "key": "apartment",
        "title": "Apartment",
        "title_pluralized": "Apartments",
        "include_in_list_view": true
      }
    ],
    "object_fields": [
      {
        "key": "price",
        "title": "Price",
        "type": "decimal",
        "object_key": "apartment"
      }
    ],
    "object_triggers": [
      {
        "title": "Welcome Trigger",
        "object_key": "apartment",
        "conditions": {
          "all": [],
          "any": [
            {"field": "priority", "operator": "is", "value": "urgent"},
            {"field": "type", "operator": "is", "value": "incident"}
          ]
        },
        "actions": [
          {"field": "assignee_id", "value": "123456789"}
        ]
      }
    ]
  }
}

New Changes Introduced

  • Added new resource type custom_objects_v2 as a valid type in requirement.json
  • Added new validation module: CustomObjectsV2
    1. Purpose: Validates limits and schema requirements for Custom Objects V2
    2. Hard limits are enforced based on existing account settings limits & product requirements
    3. Validates excessive limits in custom_objects_v2 requirements and schema for custom objects, fields & triggers

Validation Limits Enforced

Object Limits

  • Maximum 50 custom objects per account
  • Maximum 10 fields per custom object
  • Maximum 20 triggers per custom object

Field Limits

  • Maximum 5 dropdown fields per object
  • Maximum 5 multiselect fields per object
  • Maximum 10 options per dropdown/multiselect field
  • Maximum 20 conditions in relationship filters

Trigger Limits

  • Maximum 50 conditions per trigger (combined all + any)
  • Maximum 25 actions per trigger

Payload Size Limits

  • Maximum 1MB total payload size

Validation Flow

  1. Structural validation - Ensures proper JSON structure
  2. Payload size validation - Enforces 1MB limit (early return if exceeded)
  3. Limits validation - Enforces count-based limits
  4. Schema validation - Validates required fields and structure

References

JIRA

RFC

Before merging this PR

  • Fill out the Risks section
  • Think about performance and security issues

Risks

  • [RUNTIME] Can this change affect apps rendering for a user?
    No, it cannot affect apps rendering for user because code changes in this PR are not yet used for app validations. We'll uncomment the once translations for validation errors are available.
  • [HIGH | medium | low] What features does this touch?
    medium: Right now, it does not touch any existing feature. We are introducing a new resource_type which is not yet used in ZAM or any of the existing apps.

@zendesk-punavwalke zendesk-punavwalke force-pushed the zendesk-punavwalke/CRYSTAL-368-enforce-cov2-limits branch from d8fb91b to 5516c54 Compare August 16, 2025 12:43
@zendesk-punavwalke zendesk-punavwalke changed the title enforce cov2 limits in req validation [CRYSTAL-368]Enforce cov2 limits in requirment validations Aug 16, 2025
@zendesk-punavwalke zendesk-punavwalke marked this pull request as ready for review August 18, 2025 06:59
@zendesk-punavwalke zendesk-punavwalke requested a review from a team as a code owner August 18, 2025 06:59
@digesh-parecha
Copy link
Contributor

I haven't added these comments everywhere; they apply to every place where the scenario matches.
#383 (comment)
#383 (comment)

@digesh-parecha
Copy link
Contributor

Could you please include the final requirements structure in the description including all the required keys? It would be very helpful during the review process.

Copy link
Contributor

@mmassaki mmassaki left a comment

Choose a reason for hiding this comment

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

Nice work! I'm still reviewing the PR and should be adding more comments later.

@zendesk-punavwalke
Copy link
Contributor Author

@digesh-parecha @mmassaki Added final requirements structure in the description including all the required keys. And I did found the missing validations for required keys in triggers, will add those in next commit.

@zendesk-punavwalke zendesk-punavwalke force-pushed the zendesk-punavwalke/CRYSTAL-368-enforce-cov2-limits branch from 3d74473 to 2bbe4b5 Compare September 9, 2025 06:00
object_fields = requirements[SCHEMA_KEYS[:object_fields]]
object_triggers = requirements[SCHEMA_KEYS[:object_triggers]]

if all_collections_empty_or_nil?(objects, object_fields, object_triggers)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there any dependency among objects, object_fields, and object_triggers?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  • No dependency in validation logic. Each collection validates independently.
  • fields/triggers reference object_key but we don't enforce those validations here (will be done in classic). This service only handles COV2 limits, structural type and payload size validation.

Copy link
Contributor

Choose a reason for hiding this comment

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

fields/triggers reference object_key but we don't enforce those validations here.

  • Curious to know When will this validation be performed?
  • Will it allow submitting the app when there are mismatched keys between the object and the object_key in object_fields?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, current code will allow submitting the object_fields and object_triggers with invalid object references.
By invalid, I mean object_keys which does not exists in objects array.

But I will address it in next PR(Draft)

This is because it has new translation key which would take another week to be translated,hence will address this issue in next PR once translations are available.

end

[
validate_collection_is_array(objects, :invalid_objects_structure_in_cov2_requirements),
Copy link
Contributor

Choose a reason for hiding this comment

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

validate_collection_is_array and all_collections_empty_or_nil seem to do the same thing. Can we combine them into a single function?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They serve different purposes and can't be combined:

  • all_collections_empty_or_nil: returns early if all the arrays objects, fields, triggers are empty with correct error message because there is nothing to install

  • validate_collection_is_array: is for type safety of objects, fields, triggers array. It is crucial in cases below
    where requirements contains only objects so we need to ensure its also an array.

Copy link
Contributor

Choose a reason for hiding this comment

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

As discussed, let's check the possibly to combine this two methods.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@digesh-parecha I want to clarify my earlier response which caused the confusion

all_collections_empty?: It catches the specific case where someone provides
{"objects":[], "object_fields":[], "object_triggers":[]} and returns an empty_cov2_requirements error because there's nothing meaningful to install

validate_collection_is_array: validates that each field is an array and raise error for string/number/etc.) but allows empty arrays [] to pass through because this is valid requirement: {"objects":[.....], "object_fields":[], "object_triggers":[]}

I prefer to keep these validations separate since they serve different purposes

def valid_conditions_structure?(conditions)
return false unless conditions.is_a?(Hash)

(conditions.key?('all') || conditions.key?('any')) &&
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the purpose of the condition (conditions.key?('all') || conditions.key?('any'))?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  • It ensures either all or any key is present in conditions hash
  • then next line check is allorany` key is present in conditions hash then it must be an array

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Implementation is changed now

return false unless conditions.is_a?(Hash)

(conditions.key?('all') || conditions.key?('any')) &&
CONDITION_KEYS.all? { |key| conditions[key].nil? || conditions[key].is_a?(Array) }
Copy link
Contributor

Choose a reason for hiding this comment

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

How about to simplify the entire condition like this?

Suggested change
CONDITION_KEYS.all? { |key| conditions[key].nil? || conditions[key].is_a?(Array) }
CONDITION_KEYS.any? { |key| conditions[key].is_a?(Array) }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we use .any?, then a conditions hash like { "all" => [], "any" => "not_an_array" } would be considered valid, which is not correct

Copy link
Contributor

Choose a reason for hiding this comment

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

As discussed, See if it’s possible to have a single check that performs both validations.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed the implementation

let(:custom_objects_v2_requirements) { nil }
let(:errors) { described_class.call(custom_objects_v2_requirements) }

context 'when custom objects v2 requirements is not a hash' do
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd recommend a more specific description to make the intent clear.

Suggested change
context 'when custom objects v2 requirements is not a hash' do
context 'when custom objects v2 requirements is an array' do

require 'json'

describe ZendeskAppsSupport::Validations::CustomObjectsV2 do
let(:custom_objects_v2_requirements) { nil }
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we have a test example for when custom_objects_v2_requirements is nil?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, this is an invalid test CustomObjectsV2 will not receive a nil value


# ========== OBJECTS VALIDATION ==========

def validate_objects_excessive_limit(objects = [])
Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, I was wrong this comment.

I realized default args in ruby are only used when the arg is not defined, which makes it redundant as this method is never called without it.

This also applies to other methods in this class.

Suggested change
def validate_objects_excessive_limit(objects = [])
def validate_objects_excessive_limit(objects)

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.

4 participants