Skip to content

Introduce a field DSL for integrations

Alex Kalderimis requested to merge ajk-integration-field-dsl into master

What does this MR do and why?

This MR tries to improve the developer experience by introducing a DSL for defining fields on integrations.

A field includes both the field descriptor metadata, and generates prop_accessor or data_field accessors on the class.

It aims to make writing an integration class more declarative and less repetitive, but without removing access to the underlying methods if needed. Currently we have to keep the storage methods (property and data field accessors) in sync with the field names - this DSL enforces that, and would lay the basis for nice things, like being able to rename fields while keeping underlying storage the same.

This enables us to have the following DSL:

field :foo,
      required: true
      help: -> { _('Some translated help text') }

field :bar,
      section: 'numbers',
      type: 'number'
      
sensitive_field :token,
      required: true,

Instead of the current:

prop_accessor :foo
prop_accessor :bar
prop_accessor :token

def fields
  [
    {
      name: 'foo',
      required: true,
      type: 'text',
      help: _('Some translated help text')
    },
    {
      name: 'bar',
      section: 'numbers',
      type: 'number'
    },
    {
      name: 'token',
      required: true,
      type: 'password',
    }
  ]
end

POC is available in Integrations::Jira.

Future work

Section DSL

We can do the same thing for sections, and aim for an API like:

section 'heading', description: 'some description' do
  field :foo
  field :bar
end

Where each field in the section has Field#section set implicitly, and the section is registered in much the same way the fields are.

For that we do need to be careful that sections can be re-opened for addition of fields later, which is useful for subclasses.

Moving value access to Field

Currently in the field serialization entity, we rely on a one-to-one mapping between field names and instance methods to access values. This makes field renaming more onerous, and it requires adding method aliases to keep things in sync, and it isn't clear why things are related.

If we move value resolution to the field class, then we can customize it:

field :renamed,
      accessor: :old_name

This would set up a prop_accessor :old_name and use that to access the value, keeping the link between the two in one place.

Generating API configuration

Having this configuration available statically rather than purely on instances would enable us to centralize some of our other configuration and reduce the development burden for new integrations.

Hopefully, we could replace much of the manual configuration in API::Helpers::IntegrationsHelpers by generating it automatically given the available integration names and the statically available field definitions, thus unifying the UI configuration and the API configuration.

This could have a substantial code-size reduction and reduce the need to keep different layers of the application syncrhonized, leaving the models as the SSOT.

MR acceptance checklist

This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.

See: #353822 (closed)

Edited by Alex Kalderimis

Merge request reports

Loading