Tech Insights logo

Backstage Tech Insights Plugin

Visualize, understand and optimize your team's tech health.

Created by Roadie

Available on Roadie
Visualize, understand and optimize your team's tech health.

See the Tech Insights Backstage plugin in action

Installation steps

Install the plugin backend packages Backstage backend app

cd packages/backend 
yarn add @backstage/plugin-tech-insights-backend @backstage/plugin-tech-insights-node

Install the plugin frontend package in your Backstage app

cd packages/app
yarn add @backstage/plugin-tech-insights

Configure your Backstage backend to run the Tech Insights plugin

// packages/backend/src/plugins/techInsights.ts
import {
  createRouter,
  buildTechInsightsContext,
} from '@backstage/plugin-tech-insights-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';

export default async function createPlugin(
  env: PluginEnvironment,
): Promise<Router> {
  const builder = buildTechInsightsContext({
    logger: env.logger,
    config: env.config,
    database: env.database,
    discovery: env.discovery,
    factRetrievers: [], // Fact retrievers registrations you want tech insights to use, we'll add these in the next step
    factCheckerFactory: myFactCheckerFactory // Fact checker, we'll add this in the next steps
  });

  return await createRouter({
    ...(await builder),
    logger: env.logger,
    config: env.config,
  });
}

Create Fact Retrievers to fetch fact data for you

// packages/backend/src/plugins/techInsights.ts
import { FactRetriever } from '@backstage/plugin-tech-insights-node';

const myFactRetriever: FactRetriever = {
  id: 'documentation-number-factretriever', // unique identifier of the fact retriever
  version: '0.1.1', // SemVer version number of this fact retriever schema. This should be incremented if the implementation changes
  entityFilter: [{ kind: 'component' }], // EntityFilter to be used in the future (creating checks, graphs etc.) to figure out which entities this fact retrieves data for.
  schema: {
    // Name/identifier of an individual fact that this retriever returns
    examplenumberfact: {
      type: 'integer', // Type of the fact
      description: 'A fact of a number', // Description of the fact
    },
  },
  handler: async ctx => {
    // Handler function that retrieves the fact
    const { discovery, config, logger } = ctx;
    const catalogClient = new CatalogClient({
      discoveryApi: discovery,
    });
    const entities = await catalogClient.getEntities({
      filter: [{ kind: 'component' }],
    });
    /**
    * snip: Do complex logic to retrieve facts from external system or calculate fact values
    */

    // Respond with an array of entity/fact values
    return entities.items.map(it => {
      return {
        // Entity information that this fact relates to
        entity: {
          namespace: it.metadata.namespace,
          kind: it.kind,
          name: it.metadata.name,
        },

        // All facts that this retriever returns
        facts: {
          examplenumberfact: 2, //
        },
        // (optional) timestamp to use as a Luxon DateTime object
      };
    });
  },
};
const myFactRetrieverRegistration = createFactRetrieverRegistration({
  cadence: '1 * 2 * * ', // On the first minute of the second day of the month
  factRetriever: myFactRetriever,
});

Create Fact Checker checks to determine results of the facts.

// packages/backend/src/plugins/techInsights.ts
import { JsonRulesEngineFactCheckerFactory } from '@backstage/plugin-tech-insights-backend-module-jsonfc';

const myFactCheckerFactory = new JsonRulesEngineFactCheckerFactory({
  logger: env.logger,
  checks: [
    {
      id: 'exampleNumberCheck',
      type: JSON_RULE_ENGINE_CHECK_TYPE,
      name: 'Example Number Check',
      description: 'Verifies that the example number is larger is equal to 3.',
      factIds: ['documentation-number-factretriever'],
      rule: {
        conditions: {
          all: [
            {
              fact: 'examplenumberfact',
              operator: 'equal',
              value: 3,
            },
          ],
        },
      },
    },
   ],
}),

Import the plugin.

// packages/backend/src/index.ts
import techInsights from './plugins/techInsights';

const techInsightsEnv = useHotMemoize(module, () => createEnv('tech_insights'));

apiRouter.use('/tech-insights', await techInsights(techInsightsEnv));

Set up the plugin frontend.

// packages/app/src/components/catalog/EntityPage.tsx

import { EntityTechInsightsScorecardContent } from '@backstage/plugin-tech-insights';

const serviceEntityPage = (
  <EntityLayoutWrapper>
    <EntityLayout.Route path="/" title="Overview">
      {overviewContent}
    </EntityLayout.Route>
    <EntityLayout.Route path="/ci-cd" title="CI/CD">
      {cicdContent}
    </EntityLayout.Route>
    ...
    <EntityLayout.Route path="/tech-insights" title="Scorecards">
      <EntityTechInsightsScorecardContent
        title="Customized title for the scorecard"
        description="Small description about scorecards"
      />
    </EntityLayout.Route>
    ...
  </EntityLayoutWrapper>
);

Found a mistake? Update these instructions.

Things to know

How do I fetch facts for my own data?

Tech Insights uses Fact Retrievers to retrieve data from external sources. Any external service or API can be used as the service providing fact data that is tied to your Backstage entities.

How do I create non-boolean checks?

The default implementation of FactChecker uses JSON rules engine. To create checks using other types of logic, you can implement the FactCheckerFactory interface and provide your own implementation.

How do I retrieve and construct graphs from the facts I have in the database?

Tech Insights backend exposes an API endpoint that can be queried for fact data based on datetime range and entity reference. You can construct an XHR call like the following to query individual values of graphable data:

curl <backstage-backend>/api/tech-insights/facts/range?entity=<entity-kind>:<entity-namespace>/<entity-name>?ids[]=documentation-number-factretriever&startDatetime=2021-09-12T06:46:30&endDatetime=2021-10-21T06:46:30

How do I make sure my database doesn’t get overwhelmed with fact data?

You can defined a lifecycle configuration value to the factRetrieverRegistration you create. The possible values for this are either a number of items to keep or a duration for how long the item should be kept in the database before it is cleaned up. Example values are:

const maxItems = { maxItems: 7 }; // Deletes all but 7 latest facts for each id/entity pair
const ttl = { timeToLive: 1209600000 }; // (2 weeks) Deletes items older than 2 weeks
const ttlWithAHumanReadableValue = { timeToLive: { weeks: 2 } }; // Deletes items older than 2 weeks

More information

Prefer a no-code Backstage setup?

Become a Backstage expert

To get the latest news, deep dives into Backstage features, and a roundup of recent open-source action, sign up for Roadie's Backstage Weekly. See recent editions.

We will never sell or share your email address.