Functions:Implementations - bettyblocks/cli GitHub Wiki

Function Implementations

In conjunction with the function definition, you have to write the function implementation. That is the Javascript function that gets executed at runtime.

Provided to the function is the input as described in the corresponding function definition. The no-coder should have specified the actual values within de IDE.

The say-hello example

Whenever you have created a new project, it will include the say-hello example.

Its function definition (functions/say-hello/1.0/function.json):

{
  "label": "Say Hello",
  "description": "Say Hello to the world",
  "category": "Misc",
  "icon": {
   "name": "PlusIcon",
   "color": "Green"
  },
  "options": [
    {
      "meta": {
        "type": "Text"
      },
      "name": "name",
      "label": "Name",
      "info": "The name that's going to be used to say hello to the world!",
      "advanced": false,
      "configuration": {
        "placeholder": "Betty Blocks"
      }
    },
    {
      "meta": {
        "type": "Output",
        "output": {
          "type": "Text"
        }
      },
      "name": "greet",
      "label": "As",
      "info": "The resulting greet to the world."
    }
  ],
  "yields": "NONE"
}

As you can see, the sayHello function definition describes two options:

  • name - the name to say hello to (input of type Text)
  • greet - the return value containing the greet (output of type Text)

Its function implementation (functions/say-hello/1.0/index.js):

import join from 'lodash/join';

const sayHello = async ({ name }) => {
  if (name === 'oops') {
    throw new Error('Ooops. Something went wrong.');
  } else {
    return {
      greet: join(['Hello', name], ', ')
    };
  }
}

export default sayHello;

The rules of the game

The implementation should comply to the following:

  • advised is to create an asynchronous function
  • input options are contained in the first argument as an object - { name }
  • it always has to return an object corresponding to the output options - { greet }
  • it has to default export the function - export default sayHello;

Please note that the name of the function should be the lower camelcased format of the kebabcased directory name.

Remote data sources

Our platform DataAPI provides pro-coders the ability to service application data which is not stored in our "data-database". In order to facilitate this, the DataAPI works together with the ActionsJS runtime as follows:

  • the DataAPI delegates the parameters of a GQL query / mutation to an action
  • the action receives two input variables: 1) name of type Text, 2) params of type Object
  • if required, the DataAPI also passes the credentials (e.g. an API key)
  • as a response, the action should return the DataAPI compliant / expected payload

SAP Hana example

We are going to define a function which we can use for remote data taken from SAP Hana called sapData.

Its function definition:

{
  "label": "SAP Data",
  "description": "Fetch data from SAP",
  "category": "External",
  "icon": {
   "name": "ActionsIcon",
   "color": "Orange"
  },
  "options": [
    {
      "meta": {
        "type": "Text"
      },
      "name": "name",
      "label": "Name",
      "info": "The name of the Data API query."
    },
    {
      "meta": {
        "type": "Object"
      },
      "name": "params",
      "label": "Params",
      "info": "Parameters of the Data API query."
    },
    {
      "meta": {
        "type": "Output",
        "output": {
          "type": "Object"
        }
      },
      "name": "response",
      "label": "Response",
      "info": "The Data API compliant query results and the total count."
    }
  ],
  "yields": "NONE"
}

The input options:

  • name - the name of the GQL query (e.g. allBillOfMaterial)
  • params - the additional parameters of the GQL query (e.g. { select, skip, take, data_source_info })

Its function implementation:

import camelCase from 'lodash/camelCase';

const sapData = async ({ name, params }) => {
  const prefixlessName = name.replace('all', '');

  const select = params['select'];
  const info = params['data_source_info'];
  const skip = params['skip']
  const take = params['take']

  const url = info.source.host + info.model.meta_info.odata_endpoint + '?$skip=' + skip + '&$top=' + take + '&$inlinecount=allpages'
  const apiKey = info.source.api_key

  const response = await fetch(url, {
      method: 'GET',
      headers: {
        APIKey: apiKey,
        accept: 'application/json'
      }
    })
    .then(response => response.json())
    .catch(err => {
      console.error(err);
    });

  if (response.d.results.length) {
    const fetched = Object.keys(response.d.results[0]).sort();
  }

  return {
    response: {
      results: response.d.results.map(object => {
        const record =
          Object
            .keys(object)
            .reduce((o, k) => {
              o[camelCase(k)] = object[k];
              return o;
            }, {
              id: object[prefixlessName],
              createdAt: '2020-08-25T15:26:40+02:00',
              updatedAt: '2020-08-25T15:26:40+02:00',
            });

        return select
                 .reduce((o, k) => {
                   o[k] = record.hasOwnProperty(k) ? record[k] : '';
                   return o;
                 }, {});
      }),
      totalCount: parseInt(response.d.__count),
    },
  };
};

export default sapData;

So what happens is that in its essence the sapData function translates the incoming GQL query to an HTTP request towards the SAP Hana API. The JSON data which is not DataAPI compliant gets converted to what the Data API consumers expect which is { results, totalCount }.

Please note that you should return the exact requested fields or the Pages Data table component will not display any data.