Asynchronous rendering

If your perspective has several steps of computation or data fetching, you can configure it to refresh the view automatically with partial results.

To enable automatic view updates in your perspective:

  • Define an initialData function that returns the initial state of your data.

  • Define an enrich function with the processing logic of the perspective. Assign computed data to the computedData input property.

  • Use the computedData property in your render function to render your perspective.

const { DataRecord, DataModelEntity, Relation } = sirenapi;
const { EuiText, EuiTextColor, EuiIcon } = Eui;

const loading = 'Loading...';

function getInitialData() {
  return ({
    companyName: loading,
    raisedAmount: loading
  })
}

/**
  * Common function for data fetching and calculations.
  *
  * While the "enrich" operation is in progress, "render" is called
  * periodically to render the data with its most recent state.
  *
  * The 'input' object has the following properties:
  * - "source" is the raw source object representing the record data
  *
  * - "record" is a "DataRecord" instance for the displayed record
  *
  * - "dataModelEntity" is a "DataModelEntity" instance for the data model
  *   entity that prompted the record display
  *
  * - "computedData" is the object used to store fetched data and make it
  *   available to the "render" function
  *
  * - "cancelPromise" is a promise that gets rejected in case of
  *   cancellation (for example, the user changes page or closes the
  *   Record Viewer)
  */
async function enrichRecordData({ source, record, dataModelEntity, computedData }) {
  // It's best to bail out early if the script is used with an
  // unsupported data model entity.
  const rootEntity = await dataModelEntity.getRoot();
  if (await rootEntity.getLabel() !== 'Companies') {
    throw new Error('This script only works for Company records');
  }

  // We can ask for a label straight away with the getLabel() method.
  const companyName = await record.getLabel();

  // Store the fetched data using the "computedData" object. An internal
  // interval will be using updated computed data to refresh the view.
  computedData.companyName = companyName;

  // Raw field values are immediately available in the "source" parameter.
  computedData.location = source.countrycode ?
    `${source.city} (${source.countrycode})` : source.city;

  // We can fetch linked records using the "getLinkedRecords()" method of
  // the "Record" instance. We should always supply the maximum number of
  // records to return, plus the sorting order for the returned records.
  const securedInvestmentsRelation =
    (await sirenapi.Relation.getRelationsByLabel('secured'))[0];

  const investments = await record.getLinkedRecords(
    securedInvestmentsRelation,
    {
      size: 1000,
      orderBy: { field: 'raised_amount', order: 'desc' }
    }
  );

  // Now, given our investments we can calculate the total raised amount.
  computedData.raisedAmount = 0;

  for (const investment of investments) {
    const amount =
      (await investment.getFirstRawFieldValue('raised_amount')) || 0;

    computedData.raisedAmount += amount;
  }
}

function valueString(value) {
  switch (value) {
    case undefined: return <i>{loading}</i>;
    case null: return <i>No data</i>;
    default: return value;
  }
}

function currencyString(value) {
  switch (value) {
    case undefined: return <i>{loading}</i>;
    case null: return <i>No data</i>;
    case 0: return <i>No investments</i>;
    default: return `${value} $`;
  }
}

/**
 * A "view" perspective rendering function.
 *
 * Receives an "input" with "computedData" retrieved by the "enrich"
 * function.
 *
 * Returns the rendered view as a React node.
 */
function reactView({ computedData }) {
  return (
    <EuiText>
      <h1>
        <EuiTextColor color="success">
          <EuiIcon type="cheer" />
          {valueString(computedData.companyName)}
        </EuiTextColor>
      </h1>
      <p>Location: {valueString(computedData.location)}</p>
      <p>Total raised amount: {currencyString(computedData.raisedAmount)}</p>
    </EuiText>
  );
}

/**
 * A "binary" perspective rendering function.
 *
 * Like the "view" perspectives, the "binary" perspectives receive an input
 * object with the "computedData" that prompted the download.
 *
 * Binary perspectives return an object representing the static resource.
 */
async function buildJsonDownload({ computedData }) {
  const json = JSON.stringify(computedData, null, 2);
  return { filename: 'data.json', content: json };
}

/**
 * "registerPerspectives()" is called to register the "perspectives".
 *
 * The "view" property declares a list of registered "view" perspectives
 * which will be used to display the record in the user interface.
 *
 * The "binary" property declares a list of registered "binary"
 * perspectives which will be used to download the record representation.
 *
 * "initalData" is an optional property used to set the initial data prior
 * to any data fetching taking place.
 *
 * "enrich" is an optional function used to fetch data and process it. This
 * data will then be used in the "render" function.
 *
 * "render" is the function that will build the perspective output.
 */
context.registerPerspectives({
  view: {
    Tutorial: {
      initialData: getInitialData,
      enrich: enrichRecordData,
      render: reactView
    }
  },
  binary: {
    'Json report': {
      initialData: getInitialData,
      enrich: enrichRecordData,
      render: buildJsonDownload
    }
  }
});

Next steps

To make use of cached data in your script, see cached calculations.