This tutorial shows how to build a simple custom service catalog browser using the Service Catalog Items API. Your app will list service catalog items, search items by keyword, and display item details.

Topics covered in this article:

This tutorial is the second part of a series on building an employee service catalog with Zendesk:

Because the tutorials build on each other, tackle them in order and finish each tutorial before moving on to the next one.

Plan the employee experience

This tutorial builds a small web application to create the following employee experience:

  1. An employee clicks a link to open an IT service catalog, which lists available work laptops.

  2. The employee can filter the list by searching by keyword.

    Currently the catalog only contains three laptops but an IT catalog could include keyboards, mice, external monitors, cables, adapters, and more.

  3. When the employee clicks a laptop in the list, a description of the laptop appears in a side panel.

Example:

To create this experience, you’ll build a Flask web application that performs the following tasks:

  • Provides a minimal HTML UI for listing and searching service catalog items
  • Makes requests to the Zendesk API to list items, search items, and show item details

Building the service catalog browser

The service catalog application consists of four files:

  • an application file named app.py
  • two HTML templates called index.html and catalog_browser.html
  • a CSS stylesheet named main.css

The following sections describe how to create the files.

Add the application file

In your service_catalog project folder, create a file named app.py and paste the following Python code into it. Inline comments in the code explain what each function does at runtime.

import os
import requestsfrom dotenv import load_dotenvfrom flask import Flask, jsonify, request, render_template, redirect
# Load configuration values (subdomain, email, token) from a .env file in this folderload_dotenv()
# Set global variablesZENDESK_SUBDOMAIN = os.environ.get("ZENDESK_SUBDOMAIN")ZENDESK_EMAIL = os.environ.get("ZENDESK_EMAIL")ZENDESK_API_TOKEN = os.environ.get("ZENDESK_API_TOKEN")BASE_URL = f"https://{ZENDESK_SUBDOMAIN}.zendesk.com"
# Create a simple Zendesk API clientdef zendesk_get(api_path, params=None):    url = f"{BASE_URL}{api_path}"    auth = f"{ZENDESK_EMAIL}/token", ZENDESK_API_TOKEN    response = requests.get(url, params=params, auth=auth)    response.raise_for_status()    return response.json()
# Create the local web server (Flask app)app = Flask(__name__)
@app.route("/")def index():    """    Opens the IT home page    """    return render_template('index.html')

@app.route("/service_catalog", methods=('GET', 'POST'))def list_items():    """    Opens the service catalog browser.    """
    # POST request (a search request)    if request.method == 'POST':        query = request.form.get('query')        params = {"query": query}        response = zendesk_get("/api/v2/help_center/service_catalog/items/search", params=params)        catalog_items = response['service_catalog_items']        return render_template('catalog_browser.html', catalog_items=catalog_items)
    # GET request    response = zendesk_get("/api/v2/help_center/service_catalog/items")    catalog_items = response['service_catalog_items']    return render_template( 'catalog_browser.html', catalog_items=catalog_items)

@app.get("/api/items/<item_id>")def show_item(item_id):    """    Custom API endpoint to fetch a service catalog item by id.    """    response = zendesk_get(f"/api/v2/help_center/service_catalog/items/{item_id}")    item = response['service_catalog_item']    return jsonify(item)

if __name__ == "__main__":    # Start the local server    app.run(port=8000, debug=True)

Add the templates

Create a templates subfolder in your project folder and create the following files in the subfolder:

  • index.html
  • catalog_browser.html

The index.html file is the IT department's home page. Paste the following HTML and templating code into the file:

<!DOCTYPE html><html lang="en">  <head>    <meta charset="UTF-8" />    <title>IT home</title>    <link      rel="stylesheet"      href="{{ url_for('static', filename='css/main.css') }}"    />  </head>  <body>    <h2>IT home</h2>    <ul>      <li>        <a href="{{ url_for('list_items') }}">Browse the service catalog</a>      </li>    </ul>  </body></html>

The second template, catalog_browser.html, displays the service catalog. Paste the following HTML and templating code into the file:

<!DOCTYPE html><html lang="en">  <head>    <meta charset="UTF-8" />    <title>Service catalog browser</title>    <link      rel="stylesheet"      href="{{ url_for('static', filename='css/main.css') }}"    />  </head>
  <body>    <div class="row">      <div class="panel">        <h2>IT service catalog</h2>        <div>          <form            id="searchItems"            action="{{ url_for('list_items') }}"            method="POST"          >            <input type="text" id="query" name="query" />            <button type="submit" id="searchBtn">Search</button>            <button              id="listBtn"              onclick="location.href('{{ url_for('list_items') }}')"            >              List all            </button>          </form>        </div>
        {% if not catalog_items %}        <p>No results.</p>        {% else %} {% for item in catalog_items %}        <div class="item">          <button class="link" onclick="showDetails('{{item.id}}')">            <strong>{{ item.name }}</strong>          </button>          <div class="muted"><code>Item ID: {{item.id}}</code></div>        </div>        {% endfor %} {% endif %}      </div>
      <div class="panel">        <h2>Item description</h2>        <div class="muted">Click an item name to view a description.</div>        <div id="description"></div>      </div>    </div>
    <script>      // Requests the description of a catalog item and displays it in the right panel      async function showDetails(itemId) {        const url = `/api/items/${encodeURIComponent(itemId)}`        const response = await fetch(url)        const data = await response.json()        let description = `<p><strong>${data.name}</strong></p>`        description += data.description        document.getElementById("description").innerHTML = description      }    </script>  </body></html>

Add a CSS stylesheet

  1. In your project folder, create a /static/css folder and create a file named main.css in the css folder.

  2. Paste the following styles into the main.css file and save the file.

    body {  font-family: Helvetica, Arial, sans-serif;  margin: 2rem;  max-width: 980px;}input,button {  padding: 0.5rem;}.row {  display: flex;  gap: 1rem;  align-items: flex-start;}.panel {  flex: 1;  border: 1px solid #ddd;  border-radius: 8px;  padding: 1rem;}.item {  border-top: 1px solid #eee;  padding-top: 0.75rem;  margin-top: 0.75rem;}.muted {  color: #555;}#description {  font-size: 95%;  color: #555;}pre {  background: #f6f8fa;  padding: 1rem;  border-radius: 8px;  overflow-x: auto;}button.link {  background: none;  border: none;  color: #1a73e8;  padding: 0;  cursor: pointer;  text-align: left;}

The file structure of your app should look as follows:

service_catalog/  .env  static/css/    main.css  templates/    catalog_browser.html    index.html  app.py

Run the app

  1. In a terminal, navigate to your project folder and run:

    python3 app.py

    If you're working in a virtual environment, you can run python app.py.

  2. Open a browser and go to http://127.0.0.1:8000.

  3. Click an item name to view the description returned by the Show Service Catalog Item endpoint.

How it works

  1. When a user requests the catalog page, the app.py file starts by requesting a list of catalog items using the List Service Catalog Items endpoint. The app adds the items to the catalog_browser.html template and serves the page to the user's browser.

    app.py:

    response = zendesk_get("/api/v2/help_center/service_catalog/items")catalog_items = response['service_catalog_items']return render_template( 'catalog_browser.html', catalog_items=catalog_items)

    catalog_browser.html:

    {% for item in catalog_items %}<div class="item">  <button class="link" onclick="showDetails('{{item.id}}')">    <strong>{{ item.name }}</strong>  </button>  <div class="muted"><code>Item ID: {{item.id}}</code></div></div>{% endfor %}
  2. If the user enters search terms and clicks Search, the search terms are submitted to the app.py file, which requests an updated list of catalog items using the Search Service Catalog Items endpoint. The app inserts the items into the catalog_browser.html template and serves the page to the user's browser.

    catalog_browser.html:

    <form id="searchItems" action="{{ url_for('list_items') }}" method="POST">  <input type="text" id="query" name="query" />  <button type="submit" id="searchBtn">Search</button>  ...</form>

    app.py:

    if request.method == 'POST':    query = request.form.get('query')    params = {"query": query}    response = zendesk_get("/api/v2/help_center/service_catalog/items/search", params=params)    catalog_items = response['service_catalog_items']    return render_template('catalog_browser.html', catalog_items=catalog_items)
  3. If the user clicks an item in the catalog to view the details, the JavaScript on the catalog page makes a request to the /api/items/<item_id> endpoint you created in the app.py file.

    catalog_browser.html:

    async function showDetails(itemId) {  const base_app_url = "http://127.0.0.1:8000"  const url = `${base_app_url}/api/items/${encodeURIComponent(itemId)}`  const response = await fetch(url)  const data = await response.json()  let description = `<p><strong>${data.name}</strong></p>`  description += data.description  document.getElementById("description").innerHTML = description}

    The endpoint in app.py in turn calls the Show Service Catalog Item endpoint in Zendesk:

    app.py:

    @app.get("/api/items/<item_id>")def show_item(item_id):    """    Custom API endpoint to fetch a service catalog item by id.    """    response = zendesk_get(f"/api/v2/help_center/service_catalog/items/{item_id}")    item = response['service_catalog_item']    return jsonify(item)

    The custom endpoint returns the Zendesk response to the JavaScript on the page, which displays the item's description property in the details panel.

    The app.py file makes the Zendesk API call instead of the JavaScript in the browser because your API token is more secure on the server than on the browser.

Make enhancements

If you start adding many items in your catalog, consider making the following enhancements to the app:

  • Add cursor-based pagination to your zendesk_get() API client
  • Break up long lists of catalog items in the user interface with pages of items where the user can click a Next or Back button to see the next or previous page of catalog items.

Next part in the series

In the next part in the series, you'll update the catalog browser to let employees submit requests for laptops. Submitting a request automatically creates and assigns a Zendesk ticket to the IT team for review and fulfillment.

Go to Part 3 - Letting employees submit requests.