Complete template script example

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, cancelPromise, 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 "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;

  // Skip complex calculations if their results have been cached by Investigate
  if (computedData.raisedAmount === loading) {
    // 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' },
        cancelPromise // Pass the cancelPromise in the query options
      }
    );

    // 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 computed data that prompted the download.
 *
 * Binary perspective return an object representing the static resource.
 */
async function buildJsonDownload({ computedData }) {
  const json = JSON.stringify(computedData, null, 2);
  return { filename: 'data.json', content: json };
}

function buildPdfDownload({ computedData }) {
  // The identifier of the pdf template. Should be updated with your identifier.
  const templateId = 'r10S7Ky7Y';
  const filename = `${computedData.companyName}.pdf`;

  return { filename, templateId, data: computedData };
}

function buildDocxDownload({ computedData }) {
  // The identifier of the docx template. Should be updated with your identifier.
  const templateId = '1-pEMybJG';
  const filename = `${computedData.companyName}.docx`;

  return { filename, templateId, data: computedData };
}

/**
 * "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
    },
    'Pdf report': {
      initialData: getInitialData,
      enrich: enrichRecordData,
      render: buildPdfDownload
    },
    'Docx report': {
      initialData: getInitialData,
      enrich: enrichRecordData,
      render: buildDocxDownload
    }
  }
});

Next steps

For more information about scripting refer to the Scripting API documentation.