Categorizing IT Components by Energy Consumption Using Tags

Set up an automation to assign tags to IT Component Fact Sheets based on their energy consumption values.

Overview

IT components represent the technology (software and hardware) or services that an organization's applications depend on. To operate, IT components consume energy. Understanding the energy consumption levels of IT components allows organizations to effectively manage associated costs and plan their sustainability initiatives. For more information, see IT Component.

In this tutorial, you will learn how to set up an automation to assign tags to IT Component Fact Sheets based on their energy consumption values. Tags represent T-shirt sizes for each determined energy consumption range, such as XS, S, M, L, and XL.

The automation triggers every time the Fact Sheet field associated with energy consumption is updated. Based on this value, the corresponding tag representing a T-shirt size is assigned to the IT Component Fact Sheet.

Prerequisites

Before you start, do the following:

  • Get admin access to your workspace.
  • Obtain an API token by creating a technical user with admin permissions. For more information, see Creating a Technical User.
  • Prepare a mapping matrix of T-shirt sizes to energy consumption ranges for IT components in your organization's infrastructure. The energy consumption of IT components can vary significantly depending on whether the component is a piece of software, server hardware, network equipment, or other forms of technology. See an example matrix in the following table.

Example mapping of T-shirt sizes to energy consumption ranges for server hardware:

T-Shirt SizeEnergy Consumption Range
XS100-300 kWh/year
S300-700 kWh/year
M700-1,200 kWh/year
L1,200-2,500 kWh/year
XL2,500 kWh/year and higher

This tutorial assumes you have basic knowledge of:

  • Python
  • GraphQL
  • Webhooks
  • Deploying functions
  • LeanIX fact sheets and tags

Step 1: Create a Custom Attribute for Energy Consumption on IT Component Fact Sheets

Follow these steps:

  1. On the user profile, select Administration, and then select Meta Model Configuration in the sidebar.
  2. On the Meta Model Configuration page, select the IT Component Fact Sheet. You land on the Fact Sheet configuration page.
  3. In the subsection where you want to place the custom attribute, click Add field. If needed, create a new section with a subsection.
  4. In the sidebar, specify the attribute details:
    • In the Key field, enter a unique key that serves as the attribute identifier, such as EnergyConsumptionLevel.
    • In the Type list, select Integer.
    • Specify other attribute details and click Create.
    • On the tab indicated by the globe icon, enter translations for the attribute label to the target languages.
  5. In the sidebar, click Show changes, and then click Apply.

The custom attribute for energy consumption values is added to IT Component Fact Sheets.

Step 2: Create a Tag Group for T-Shirt Sizes

Create a tag group with tags representing T-shirt sizes. You can do it in the application user interface (UI) or through the GraphQL API. To learn how to manage tag groups in the application UI, see Tagging.

When creating a tag group, do the following:

  • Restrict the tag group to IT Component Fact Sheets.
  • Set the group mode to Single.

Step 3: Create a Webhook for Energy Consumption Updates

Create a webhook that triggers upon updates to the EnergyConsumptionLevel field on IT Component Fact Sheets.

Follow these steps:

  1. On the user profile, select Administration, and then select Webhooks in the sidebar.

  2. On the Webhooks page, click New Webhook.

  3. Specify the webhook details:

    • Triggering Events: Select FACT_SHEET_UPDATED.
    • Managing User: Select a Technical User with admin permissions.
    • Type: Select PUSH.
    • Target URL: Enter the URL of the endpoint to send webhook notifications to. Before you complete other steps in this tutorial, enter a test target URL. To get a test URL, use tools such as Webhook.site.
  4. Optional: In the Callback field, enter JavaScript code to manipulate the webhook payload.

    Example code:

    var payload = delivery.payload;
    delivery.active = false;
    var tempDict = {};
    tempDict['fields'] = [];
    var attributeList = ['EnergyConsumptionLevel'];
    
    if (payload.factSheet.type === 'ITComponent') {
        tempDict['id'] = payload.factSheet.id;
        tempDict['type'] = payload.factSheet.type;
        tempDict['rev'] = payload.factSheet.rev;
    
        for (var i = 0; i < payload.factSheet.fields.length; ++i) {
            if (attributeList.indexOf(payload.factSheet.fields[i].name) >= 0) {
                tempDict.fields.push({ key: payload.factSheet.fields[i].name, value: payload.factSheet.fields[i].data });
            }
        }
    
        if (tempDict['fields'].length > 0) {
            delivery.payload = tempDict;
            delivery.active = true;
        }
    }
    

    With the provided callback, the webhook payload appears as follows.

    Example webhook payload:

    {
        "fields": [
            {
                "key": "EnergyConsumptionLevel",
                "value": {
                    "value": 820,
                    "type": "IntegerValue"
                }
            }
        ],
        "id": "cb943942-39fd-4b45-8909-916b99c1286a",
        "type": "ITComponent",
        "rev": 15
    }
    
  5. Optional: Specify other optional webhook details.

  6. Click Create.

A webhook with a test target URL is created. To test the webhook, update the energy consumption value on an IT Component Fact Sheet and verify the payload sent to the target URL.

Step 4: Create a Function to Assign Tags

Create a function to assign tags representing T-shirt sizes based on energy consumption ranges to IT Component Fact Sheets. You can use your preferred Function as a Service provider.

The function should do the following:

  • Perform authentication to LeanIX services. For more information, see Authentication to LeanIX Services.

  • Parse the webhook payload that contains the following:

    • The updated value of the EnergyConsumptionLevel field
    • The ID of the associated IT Component Fact Sheet
  • Identify the tag to be assigned to the Fact Sheet based on the energy consumption value.

  • Retrieve the IDs of tags from the created tag group using a GraphQL query.

    Example query:

    {
        allTags(filter: {tagGroupName: "Energy Consumption"}) {
            edges {
                node {
                    id
                    name
                }
            }
        }
    }
    

    Example response:

    {
        "data": {
            "allTags": {
                "edges": [
                    {
                        "node": {
                            "id": "db9fbe30-8c75-4d3b-b498-e30b55da0872",
                            "name": "XS"
                        }
                    },
                    {
                        "node": {
                            "id": "6977e724-d2ec-4692-b59b-fe47252ce28a",
                            "name": "S"
                        }
                    },
                    {
                        "node": {
                            "id": "6b1e4df0-fea5-403f-90d5-755f79a5af9c",
                            "name": "M"
                        }
                    },
                    {
                        "node": {
                            "id": "f84775e5-d57b-436c-8e37-342320e1aa21",
                            "name": "L"
                        }
                    },
                    {
                        "node": {
                            "id": "0b16ee28-7c9d-43a9-b735-14d18a95a999",
                            "name": "XL"
                        }
                    }
                ]
            }
        }
    }
    
  • Assign the appropriate tag to the Fact Sheet using a GraphQL mutation. For more information, see Add Tags to a Fact Sheet.

Example code:

import json
import logging
import os
import requests


logging.basicConfig(level=logging.INFO)

# Add a timeout to prevent the request hanging
TIMEOUT = 10

# API token and Subdomain are set as env variables.
# FaaS services support environment variables, it is adviced not to hard code
# sensitive information in your code.

LEANIX_API_TOKEN = os.getenv('LEANIX_API_TOKEN')
LEANIX_SUBDOMAIN = os.getenv('LEANIX_SUBDOMAIN')
LEANIX_GRAPHQL_URL = f'https://{LEANIX_SUBDOMAIN}.leanix.net/services/pathfinder/v1/graphql'
LEANIX_OAUTH2_URL = f'https://{LEANIX_SUBDOMAIN}.leanix.net/services/mtm/v1/oauth2/token'

# `T_SHIRT_SIZES` is a mapping of t-shirt sizes to their IDs, based on the output
# of a GraphQL query. Alternatively, you can programmatically call the query and
# parse the response, but this introduces overhead due to additional network
# requests.

T_SHIRT_SIZES = {
    "XS": "db9fbe30-8c75-4d3b-b498-e30b55da0872",
    "S": "6977e724-d2ec-4692-b59b-fe47252ce28a",
    "M": "6b1e4df0-fea5-403f-90d5-755f79a5af9c",
    "L": "f84775e5-d57b-436c-8e37-342320e1aa21",
    "XL": "0b16ee28-7c9d-43a9-b735-14d18a95a999",
}

def _obtain_access_token():
    """Obtains a LeanIX Access token using the Technical User generated
    API secret.

    Returns:
        str: The LeanIX Access Token
    """
    if not LEANIX_API_TOKEN:
        raise Exception('A valid token is required')
    response = requests.post(
        LEANIX_OAUTH2_URL,
        auth=("apitoken", LEANIX_API_TOKEN),
        data={"grant_type": "client_credentials"},
    )
    response.raise_for_status()
    return response.json().get('access_token')

def _determine_t_shirt_size(payload):
    """Determines the appropriate T-shirt size tag based on the extracted energy 
    consumption value from the parsed payload.

    Args:
        payload (dict): The webhook payload

    Returns:
        Optional[str]: The t-shirt size of the energy consumption value.
    """
    energy_consumption_level = None
    t_shirt_size = None

    # Loop through the payload fields
    for field in payload["fields"]:
        # Extract the `EnergyConsumptionLevel` value from the payload, convert it to 
        # an integer, and store it in the `energy_consumption_level` variable. 
        # Stop iterating the loop once the value has been found.
        if field.get("key") == "EnergyConsumptionLevel":
            energy_consumption_level = field.get("value", {}).get("value")
            break

    # Check the Energy Consumption t-shirt size
    if energy_consumption_level is not None:
        if 100 <= energy_consumption_level <= 300:
            t_shirt_size = "XS"
        elif 300 <= energy_consumption_level <= 700:
            t_shirt_size = "S"
        elif 700 <= energy_consumption_level <= 1200:
            t_shirt_size = "M"
        elif 1200 <= energy_consumption_level <= 2500:
            t_shirt_size = "L"
    return t_shirt_size

def update_fact_sheet(fact_sheet_id, t_shirt_size)
    """Tags the specified FactSheet with the appropriate t-shirt size
    Energy Consumption tag.

    Args:
        fact_sheet_id (str): The FactSheet UUID to be updated.
        t_shirt_size (str): The t-shirt size (XS, S, M, L, XL).
    """
    # Fetch the `id` of the t-shirt size
    t_shirt_size_id = T_SHIRT_SIZES.get(t_shirt_size)
    # If there is no match `id` bail out
    if not t_shirt_size_id:
        logging.warning(f"Could not retrieve `id` for t-shirt size: {t_shirt_size}")
        return

    # The GraphQL mutation to `replace` the tag of the FactSheet
    # `value` is a stringified list of the JSON object.
    # NOTE: Use `\\` to escape the `value` fields
    graphql_mutation = """
    mutation {
        updateFactSheet(
            id: "%s", 
            patches:[
                {
                    op: replace, 
                    path: "/tags", 
                    value: "[{\\"tagId\\": \\"%s\\"}]"
                }
            ]
        ){
            factSheet {
                id
                name
                tags {
                    id
                    name
                }
            }
        }
    }
    """ % (fact_sheet_id, t_shirt_size_id)
    
    # Fetch the access token and set the Authorization Header
    access_token = _obtain_access_token()
    auth_header = f'Bearer {access_token}'
    # Provide the headers
    headers = {
        "Authorization": auth_header,
    }
    
    response = requests.post(
        LEANIX_GRAPHQL_URL,
        data=json.dumps({"query": graphql_mutation}),
        timeout=TIMEOUT,
        headers=headers,
    )
    response.raise_for_status()
    response_data = response.json()
    # GraphQL always returns a 200 response even if errors are included
    # as such we check if `errors` is not empty.
    errors=response_data.get("errors", [])
    if len(errors):
        logging.error(f"Request was not successfull: {errors}")
        return
    logging.info(f"GraphQL mutation success: {response_data}")

def event_handler(payload):
    """Receives the webhook payload, extracts relevant information, 
    and initiates a GraphQL mutation to add the corresponding tag group.

    Args:
        payload (dict): The webhook payload
    """
    if payload:
        fact_sheet_id = payload.get("id")
        t_shirt_size = _determine_t_shirt_size(payload)
        if fact_sheet_id and t_shirt_size:
            update_fact_sheet(fact_sheet_id, t_shirt_size)
        else:
            logging.error("Could not update FactSheet")

Before proceeding, test your function locally. When ready, deploy the function. For instructions, refer to the documentation of your FaaS provider.

Step 5: Update the Webhook with the Function URL

Once you've successfully tested the function, update the webhook with the function URL.

Follow these steps:

  1. On the user profile, select Administration, and then select Webhooks in the sidebar.
  2. On the Webhooks page, select the webhook that you created.
  3. In the Target URL field, enter the function URL.
  4. Click Save.

The automation is set up. To ensure that it works as expected, update the energy consumption value on a test Fact Sheet and verify that the corresponding tag is assigned.