Tables with Custom Reports

Create a report, formatted as a table which provides the ability to export data to excel.

Overview

Custom reports are a great way for analysing and communicating Enterprise Architecture insights of your organization in an effective way.

In this step-by-step tutorial you will create a simple LeanIX custom report, from scratch, that displays a table of Applications with the corresponding tag count and tag names, and exports it as an Excel file, as in the picture below:

Pre-requisites

Getting Started

Install the leanix-reporting-cli globally via npm:

npm install -g @leanix/reporting-cli

Initialize a New Project

mkdir table-report-tutorial
cd table-report-tutorial
lxr init
npm install

Configure your Environment

Configure your environment by editing editing the lxr.json file, if required:

{
  "host": "app.leanix.net",
  "apitoken": "Jw8MfCqEXDDubry64H95SYYPjJTBKNFhkYD8kSCL"
}

📘

Host

If you have implemented SSO the host should be the domain for your workspace. The domain is visible in the url for your workspace.

If you are a US or EU customer and you do Not have SSO enabled. The host can be any of the following us.leanix.net, eu.leanix.net, or app.leanix.net. In order to determine which is the correct option, please look at the url for your workspace.

After completing these steps, your project structure should match the image below:

Clean up your Project Folder

Start by cleaning up our project folder, deleting the unnecessary files:

  • src/report.js
  • src/fact-sheet-mapper.js
  • src/assets/bar.css
  • src/assets/main.css

Your project folder structure should now match the image below:

Installing Dependencies

For this project we will be using Alpine.js, a lightweight framework which allows us to embed Javascript directly in our HTML code. For that, we start by adding the following dependencies to our project:

npm install alpinejs

Project Javascript, HTML, and CSS

Please note the code tabs below, please see the All tab for the entire code snippet.

javascript
import 'alpinejs'
import '@leanix/reporting'


const state = {
  baseUrl: '',
  // Will hold the dataset fetched from the workspace
  applications: [],
  // The column definition for our table
  columns: [
    {
      key: 'id',
      header: 'ID'
    },
    {
      key: 'name',
      header: 'Name'
    },
    {
      key: 'tagCount',
      header: 'Tag Count'
    },
    {
      key: 'tags',
      header: 'Tags'
    }
  ]
}
const methods = {
  // to be called upon report initialization
  initializeReport() {
    return lx.init()
      .then(setup => {
        this.baseUrl = setup.settings.baseUrl
        const config = {
          allowTableView: false,
          facets: [
            {
              fixedFactSheetType: 'Application',
              attributes: ['name', 'tags {name tagGroup {name}}'],
              callback: applications => this.applications = applications
                .map(application => {
                  let { tags = [] } = application
                  const tagCount = tags.length
                  tags = tags.map(tag => {
                    const { name, tagGroup = null } = tag
                    let labelPrefix = ''
                    if (tagGroup !== null) labelPrefix = `${tagGroup.name} - `
                    return `${labelPrefix}${name}`
                  }).join(', ')
                  return { ...application, tags, tagCount }
                })
            }
          ]
        }
        return lx.ready(config)
      })
  }
}

window.init = () => {
  return {
    ...state,
    ...methods
  }
}
javascript
import 'alpinejs'
import '@leanix/reporting'


const state = {
  baseUrl: '',
  // Will hold the dataset fetched from the workspace
  applications: [],
  // The column definition for our table
  columns: [
    {
      key: 'id',
      header: 'ID'
    },
    {
      key: 'name',
      header: 'Name'
    },
    {
      key: 'tagCount',
      header: 'Tag Count'
    },
    {
      key: 'tags',
      header: 'Tags'
    }
  ]
}

const methods = {
  // to be called upon report initialization
  initializeReport() {
    return lx.init()
      .then(setup => {
        this.baseUrl = setup.settings.baseUrl
        const config = {
          allowTableView: false,
          facets: [
            {
              fixedFactSheetType: 'Application',
              attributes: ['name', 'tags {name tagGroup {name}}'],
              callback: applications => this.applications = applications
                .map(application => {
                  let { tags = [] } = application
                  const tagCount = tags.length
                  tags = tags.map(tag => {
                    const { name, tagGroup = null } = tag
                    let labelPrefix = ''
                    if (tagGroup !== null) labelPrefix = `${tagGroup.name} - `
                    return `${labelPrefix}${name}`
                  }).join(', ')
                  return { ...application, tags, tagCount }
                })
            }
          ]
        }
        return lx.ready(config)
      })
  }
}

window.init = () => {
  return {
    ...state,
    ...methods
  }
}

Editing the HTML

Edit the index.html file as follows. Notice the init method, called by the x-data AlpineJS directive of the body element, which first initializes the context for our report. After the context is set, the x-init AlpineJS directive, also of the body element, triggers the initializeReport method:

html
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <meta name="application-name" content="table-report-tutorial">
  <meta name="description" content="%description%">
  <meta name="author" content="%author%">

  <title>%title%</title>

</head>
<body x-data="init()" x-init="initializeReport()">
  <table>
    <thead>
      <tr>
        <template x-for="column in columns">
          <th x-text="column.header"></th>
        </template>
      </tr>
    </thead>
    <tbody>
      <template x-for="application in applications">
        <tr>
          <template x-for="column in columns">
            <td x-text="application[column.key]"></td>
          </template>
        </tr>
      </template>
    </tbody>
  </table>
</body>
</html>

After modifying the index.html and index.js files, launch the development server by using the following command:

npm start

❗️

Attention

Please take into consideration the Security Hint paragraph and these documentation notes for dealing with the security warnings when launching the development server for the first time.

After the development server launches, you should get an output similar to the picture below.

Success, our table renders correctly! However it is clear that it could benefit from some styling. Let's make that happen!

Styling the Report with CSS

We will be using Tailwindcss for styling our report. Tailwindcss is a low-level CSS framework that provides a set of utility classes that can be included directly into our HTML code.

The additional dependencies should be added through the following commands:

npm install --save-dev postcss-loader
npm install tailwindcss

In order to use Tailwindcss, you need to modify the webpack.config.js file by including the 'postcss-loader' option, as indicated by the red arrow in the picture below:

Next create a postcss.config.js file in the src folder, with the following content:

javascript
module.exports = {
  plugins: [
    require('tailwindcss'),
    require('autoprefixer')
  ]
}

Additionally, create an tailwind.css file in the assets folder with the following content:

@tailwind base;
@tailwind components;
@tailwind utilities;

Finally import the tailwind.css file into our index.js file at the top, as follows:

import 'alpinejs'
import '@leanix/reporting'
import './assets/tailwind.css'
...

Your project folder should match the image below:

Update HTML By Adding tailwind Classes

Edit the index.html file again by adding some tailwind classes to the table elements:

<body x-data="init()" x-init="initializeReport()">
  <!--  we add this wrapper element around our table -->
  <div class="container mx-auto h-screen">
    <!-- and we add tailwindcss utility classes to our already existing table elements -->
    <table class="table-auto w-full text-center text-xs">
      <thead class="bg-black text-white sticky top-0">
        <tr>
          <template x-for="column in columns">
            <th class="px-4 py-2" x-text="column.header"></th>
          </template>
        </tr>
      </thead>
      <tbody>
        <template x-for="application in applications">
          <tr class="hover:bg-gray-100 transition-color duration-150 ease-in-out">
            <template x-for="column in columns">
              <td class="border px-4 py-2" x-text="application[column.key]">
              </td>
            </template>
          </tr>
        </template>
      </tbody>
    </table>
  </div>
</body>
...

The output of our report looks now much better, doesn't it?

Implementing the Export to Excel Feature

The report is not yet complete, as it still missing the "export to excel feature". It is worth noticing that although the leanix-reporting framework provides an out-of-the-box export to excel feature, we will be utilizing a custom solution for it since our table displays a couple of locally computed columns - tag count and tags.

Install Dependencies

For that, use ExcelJS by adding the following additional dependencies to our project:

npm install exceljs file-saver

In the index.js file, import the exceljs and the file-saver libraries and add the exportToXLSX method as follows:

javascript
import 'alpinejs'
import '@leanix/reporting'
import './assets/tailwind.css'
import Excel from 'exceljs'
import { saveAs } from 'file-saver'

const methods = {
    initializeReport() {
    ...
  },
    exportToXLSX (columns, applications) {
        lx.showSpinner()
        const workbook = new Excel.Workbook()
        const worksheet = workbook.addWorksheet('Applications')
        worksheet.columns = columns
        worksheet.addRows(applications)
        return workbook.xlsx.writeBuffer()
            .then(buffer => {
                const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
                saveAs(blob, 'document.xlsx')
                lx.hideSpinner()
            })
            .catch(err => console.error('error while exporting to excel', err))
    }
}

Implement Export Button Within HTML

We also need a button in our custom report user-interface to trigger the exportToXLSX method. We implement it by editing the index.html:

<body x-data="init()" x-init="initializeReport()">
  <div class="container mx-auto h-screen">
    <!-- wrapper for our export-to-excel button -->
    <div class="flex justify-between items-center py-4">
      <!-- additinonally we display the number of listed applications -->
      <span class="text-sm italic" x-text="'Listing ' + applications.length + ' Applications'"></span>
      <!-- and the export-to-excel button that triggers the exportToXLSX method-->
      <button
        class="bg-blue-500 hover:bg-blue-700 text-white text-xs font-bold py-1 px-2 rounded"
        @click="exportToXLSX(columns, applications)">
        Export to XLSX
      </button>
    </div>
    <table class="table-auto w-full text-center text-xs">
      ...
    </table>
  </div>
</body>

Launch the development server, you should see the table table and export button:

We quickly realize when exporting the dataset that the exported document doesn't look that great. Altough the styling of the excel document falls out of the scope for this tutorial, we can improve it significantly by simply specifying the width of each exported column in our index.js file as follows:

javascript
...
const state = {
  baseUrl: '',
  applications: [],
  columns: [
    {
      key: 'id',
      header: 'ID',
      width: 40
    },
    {
      key: 'name',
      header: 'Name',
      width: 60
    },
    {
      key: 'tagCount',
      header: 'Tag Count',
      width: 10
    },
    {
      key: 'tags',
      header: 'Tags',
      width: 100
    }
  ]
}

And that's it! Your custom report is now ready to be reviewed!

Congratulations on completing your custom table report!