opencrvs / opencrvs-core

A global solution to civil registration
https://www.opencrvs.org
Other
90 stars 73 forks source link

New form input type: Fetch button v2 #7489

Open rikukissa opened 3 months ago

rikukissa commented 3 months ago

Description

In all of its simplicity, this form input is a button, which when pressed, creates an HTTP request and returns the value back to the form. This input is meant to be a very low-level, generic building block, not many abstraction levels from a pure fetch call. As such, it can be used for countless use cases. The field can form HTTP payloads using values from other form fields. Other form fields can again read the HTTP return values from this field.

Image Image Image

The architectural steer for building this is that adding it should be low-risk and disrupt existing code as little as possible.

Example usage of the new

In this example, there is a button in the form that generates an NUI (National Unique Identifier) when pressed. Button is disabled after it has been pressed once. There is a disabled text field below the button in where the resulting value is shown.

Image

{
  name: 'fetchNUI',
  type: 'HTTP_BUTTON',
  custom: true,
  validator: [],
  conditionals: [
    /*
      * The form field can also refer to its own values through the
      * $form object. This is useful for instance if button should be disabled
      * while the request is loading or if the request has already been made.
      */
    {
      action: 'disable',
      expression: '$form.fetchNUI?.data'
    },
    {
      action: 'disable',
      expression: '$form.fetchNUI?.error'
    }
  ],
  options: {
    /*
     * type: Omit<RequestInit, 'body'> & {url: string} & {body: Record<string, any>}
     *
     * Component should read all string fields and replace variables so that
     * an URL like /api/record/{$draft.id} could be used
     * Applies to at least url, body, headers
     * The syntax doesn't have to be exactly this – I took the inspiration from how react-intl does it
     * If this is not needed for Madagascar, please draft a new ticket for adding this feature and leave it unimplemented for now.
     */
    url: '/api/v1/nui',
    method: 'GET',
    headers: {
      authorization: 'Bearer {$user.token}'
    }
  },
  label: {
    defaultMessage: 'Generate NUI',
    description: 'Label for form field: First names',
    id: 'form.field.label.nui'
  }
},
{
  name: 'nui',
  type: 'TEXT',
  custom: true,
  validator: [],
  dependency: 'fetchNUI',
  /*
    * This is a problematic place because "initialValueKey" implementation
    * Currently reads draft data values, not Formik field values which we want here
    *
    * So there is a change to make:
    * - Change the field to work with the syntax below, so that $values, $form, $draft etc are in the root of the path
    *
    * Research if anyone is using this field:
    * If not, then change it to work with the syntax below and document a breaking change
    * If yes, then lets make it so the new $values, $form, $draft points to new variables while we leave the plain `values` as is. So 
    * pointing to the draft (?)
    */
  initialValueKey: '$form.fetchNUI.data.exampleDataFromServer',
  conditionals: [
    {
      action: 'disable',
      expression: 'true'
    },
    {
      action: 'hide',
      // In expressions, keep the old "value" reference for now but also introduce $value
      expression: '!$form.fetchNUI?.data'
    }
  ],
  label: {
    defaultMessage: 'NUI',
    description: 'Label for form field: First names',
    id: 'form.field.label.nuiText'
  }
},

Note that while researching this, I started a branch for this in core

Dev tasks

eduffus commented 3 months ago

@SyedaAfrida we will need to modify the Farajaland configuration to make sure this is consistently tested.

naftis commented 3 months ago

A proposal to generalize this for MOSIP as well:

HTTP & BUTTON & REDIRECT_BUTTON

{
  name: 'fetchNUI',
  type: 'HTTP',
  custom: true,
  options: {
    url: '/api/v1/nui',
    method: 'GET',
    headers: {
      authorization: 'Bearer {$user.token}'
    },
    body: {
      nonce: '$form.redirectNIN'
    }
  }
}
{
  "name": "redirectNIN",
  "type": "REDIRECT_BUTTON",
  "custom": true,
  "options": {
    "trigger": "fetchNUI",
    "url": "https://mosip-mediator.farajaland.opencrvs.org",
  },
  "label": {
    "defaultMessage": "Redirect to NIN",
    "description": "Label for redirect button",
    "id": "form.field.label.redirectNIN"
  },
  conditionals: [
    {
      action: 'disable',
      expression: 'true'
    },
    {
      action: 'hide',
      expression: '$form.redirectNIN' // this value is populated using a query parameter `?%24form.redirectNIN=...` where %24 is $
    }
  ]
}
rikukissa commented 3 months ago

Let's take this ticket into account as well https://github.com/opencrvs/opencrvs-core/issues/5987

tahmidrahman-dsi commented 2 months ago

An example of country configuration:

{
  name: 'fetchNUI',
  type: 'HTTP',
  hideInPreview: true,
  custom: true,
  label: { id: 'form.label.empty', defaultMessage: ' ' },
  validator: [],
  options: {
    url: '/api/countryconfig/nui-demo',
    method: 'GET'
  }
},
// {
//   name: 'redirect',
//   type: 'REDIRECT',
//   custom: true,
//   label: { id: 'form.label.empty', defaultMessage: ' ' },
//   validator: [],
//   initialValue: '',
//   conditionals: [
//     { action: 'hide', expression: '!$form.fetchNUI?.data' }
//   ],
//   options: {
//     url: '/some-route?data=${$form.fetchNUI?.data}'
//   }
// },
{
  name: 'iD',
  type: 'TEXT',
  label: formMessageDescriptors.iDTypeNationalID,
  required: true,
  custom: true,
  initialValue: {
    expression: '$form.fetchNUI?.data',
    dependsOn: ['fetchNUI']
  },
  maxLength: 10,
  conditionals: [
    {
      action: 'hide',
      expression:
        '!$form.fetchNUI?.data || !window.navigator.onLine'
    },
    {
      action: 'disable',
      expression: '$form.fetchNUI?.data'
    }
  ],
  validator: [],
  mapping: {
    template: {
      fieldName: 'childNID',
      operation: 'identityToFieldTransformer',
      parameters: ['id', 'NATIONAL_ID']
    },
    mutation: {
      operation: 'fieldToIdentityTransformer',
      parameters: ['id', 'NATIONAL_ID']
    },
    query: {
      operation: 'identityToFieldTransformer',
      parameters: ['id', 'NATIONAL_ID']
    }
  }
},
{
  name: 'dummyButton',
  type: 'BUTTON',
  custom: true,
  hideInPreview: true,
  validator: [],
  options: {
    trigger: 'fetchNUI',
    shouldHandleLoadingState: true
  },
  conditionals: [
    {
      action: 'hide',
      expression: '$form.fetchNUI?.data'
    },
    {
      action: 'disable',
      expression: '$form.fetchNUI?.error'
    },
    {
      action: 'disable',
      expression: '!window.navigator.onLine'
    }
  ],
  label: formMessageDescriptors.iDTypeNationalID,
  buttonLabel: {
    defaultMessage: 'Generate NUI',
    description: 'Label for form field: Generate NUI',
    id: 'form.field.label.generateNUI'
  },
  icon: 'UserCircle',
  loadingLabel: {
    defaultMessage: 'Generating...',
    description: 'Label for form field: Generate NUI',
    id: 'form.field.label.generatingNUI'
  }
},
{
  name: 'iDManual',
  type: 'TEXT',
  label: {
    id: 'form.field.label.idManual',
    defaultMessage: 'Enter national ID manually'
  },
  required: true,
  custom: true,
  initialValue: '',
  maxLength: 10,
  conditionals: [
    {
      action: 'hide',
      expression: 'window.navigator.onLine'
    }
  ],
  validator: [],
  mapping: {
    template: {
      fieldName: 'childNIDManual',
      operation: 'identityToFieldTransformer',
      parameters: ['id', 'NATIONAL_ID']
    },
    mutation: {
      operation: 'fieldToIdentityTransformer',
      parameters: ['id', 'NATIONAL_ID']
    },
    query: {
      operation: 'identityToFieldTransformer',
      parameters: ['id', 'NATIONAL_ID']
    }
  }
}