Building a DDL (Dynamic dropdown list) operation


Through CDK, you can also build operations that use dropdown lists on the operation's input properties panel.

We will extend the TMDB connector example from the quickstart and add another operation list_movie_genres_ddl.

At the end of this walkthrough, you will have a DDL operation, which can later be called from other operations:

ddl-end-result

Prerequisites


You have followed the quickstart and deployed your first connector.

Testing 3rd party endpoints


You will need the Movie Genres endpoint from TMDB API to build the list_movies_genres_ddl operation.

Hitting Try it! for Movie Genres produces an array of Genres with id and name of each genre object.

movie-genres-tryit

Development


Now, you can initiate development with the information on inputs and outputs for the endpoint.

info

The following steps should be performed in an IDE, such as VS code in the TMDB connector project that was built in the quickstart.

Add DDL operation


Add a new operation using:

tray-cdk connector add-operation list_movie_genres_ddl composite

Where list_movie_genres_ddl is the operation name and composite is the operation type.

This would add a new folder under src with the following files:

operation-folder

Now, we are ready to modify these files to build our operation.

input.ts


The input object should be blank, as our endpoint requires no inputs.

Here's the complete input.ts for your reference:

Copy
Copied
export type ListMovieGenresDdlInput = {};

output.ts


You must use a specific type: DDLOperationOutput for a DDL operation.

Here's the complete Output.ts file for reference:

Copy
Copied
import { DDLOperationOutput } from "@trayio/cdk-dsl/connector/operation/OperationHandler";

export type ListMovieGenresDdlOutput = DDLOperationOutput<number>;

Note the DDLOperationOutput<number>. It can be either string or number, depending on the data type of the value field.

Read the following section to understand how to determine the datatype of DDLOperationOutput.

Explanation for DDL types

Every DDL operation should return an array of objects where each object represents one option in the dropdown that will be produced. Each option object should have a text and value field.

While text represents the field's display value' that will be visible on the UI, value represents the API value that will be used by the operation when making the HTTP call.

If you examine the output from the API Try it button for Movie Genres endpoint,

Copy
Copied
{
  "genres": [
    {
      "id": 28,
      "name": "Action"
    },
    {
      "id": 12,
      "name": "Adventure"
    },
    ...More genres
  ]
}

Notice that every genre has an id and a name.

When we build the operation get_movies_by_genre, it will use the genre IDs to get a list of movies.

Hence, genre id will be the API value, and genre name will be the display value, i.e., visible on UI in the props panel of the connector.

The API value id in a number as seen in the JSON response above, hence we use DDLOperationOutput<number>, you will use string when the operation that calls the DDL needs a string

handler.ts


First of all, clear the contents of the handler function so it looks like this:

Copy
Copied
export const listMovieGenresDdlHandler =
    OperationHandlerSetup.configureHandler<AadiTmdbAuth, ListMovieGenresDdlInput, ListMovieGenresDdlOutput>((handler) =>
        handler.usingComposite(async (ctx, input, invoke) => {
            //Remove the default content
        })
    );

In the handler function, we need to call the Movie Genres endpoint.

Here is how you can do it via axios:

Copy
Copied
const genresListResponse = await axios.get(
  `https://api.themoviedb.org/3/genre/movie/list`,
  {
    headers: {
      Authorization: `Bearer ${ctx.auth?.user.access_token}`,
    },
  }
);

For error handling, you can check the status field of the response; if it's not 200 you can throw the error as shown below:

Copy
Copied
if (genresListResponse.status !== 200) {
  return OperationHandlerResult.failure(
    OperationHandlerError.connectorError("Network call failed")
  );
}

DDL operations should always return a response in the format below:

Copy
Copied
{
    results: [
        {
            text: 'The text you want to display for the option'
            value: 'The API value for the option'
        },
        {
            text: 'The text you want to display for the option'
            value: 'The API value for the option'
        },
        ...
    ]
}

Once we have the response, we can transform it into the schema required by DDL operations, as shown here:

Copy
Copied
const genresList: GenreList[] = genresListResult.genres.map(
  (genre: GenreObject) => {
    return {
      text: genre.name,
      value: genre.id,
    };
  }
);

Here's the complete handler.ts for reference:

Copy
Copied
import { OperationHandlerSetup } from "@trayio/cdk-dsl/connector/operation/OperationHandlerSetup";
import { AadiTmdbAuth } from "../AadiTmdbAuth";
import { ListMovieGenresDdlInput } from "./input";
import { ListMovieGenresDdlOutput } from "./output";
import {
  OperationHandlerError,
  OperationHandlerResult,
} from "@trayio/cdk-dsl/connector/operation/OperationHandler";
import axios from "axios";

type GenreObject = {
  id: number;
  name: string;
};

type GenreList = {
  text: string;
  value: number;
};

export const listMovieGenresDdlHandler = OperationHandlerSetup.configureHandler<
  AadiTmdbAuth,
  ListMovieGenresDdlInput,
  ListMovieGenresDdlOutput
>((handler) =>
  handler.usingComposite(async (ctx, input, invoke) => {

    const genresListResponse = await axios.get(
      `https://api.themoviedb.org/3/genre/movie/list`,
      {
        headers: {
          Authorization: `Bearer ${ctx.auth?.user.access_token}`,
        },
      }
    );

    if (genresListResponse.status !== 200) {
      return OperationHandlerResult.failure(
        OperationHandlerError.connectorError("Network call failed")
      );
    }

    const genresList: GenreList[] = genresListResponse.data.genres.map(
      (genre: GenreObject) => {
        return {
          text: genre.name,
          value: genre.id,
        };
      }
    );

    return OperationHandlerResult.success({
      result: genresList,
    });
  })
);

handler.test.ts


A simple test for this endpoint could be to check the total number of genres returned in the response.

A Try it on the docs gives us 19 results.

We can test this with:

expect(outputValue.results.length).toEqual(19);

Here's the complete handler.test.ts for reference:

Copy
Copied
import { OperationHandlerTestSetup } from "@trayio/cdk-dsl/connector/operation/OperationHandlerTest";
import { OperationHandlerResult } from "@trayio/cdk-dsl/connector/operation/OperationHandler";
import { listMovieGenresDdlHandler } from "./handler";
import "@trayio/cdk-runtime/connector/operation/OperationHandlerTestRunner";

OperationHandlerTestSetup.configureHandlerTest(
  listMovieGenresDdlHandler,
  (handlerTest) =>
    handlerTest
      .usingHandlerContext("test")
      .nothingBeforeAll()
      .testCase("should return 19 genres", (testCase) =>
        testCase
          .givenNothing()
          .when(() => ({}))
          .then(({ output }) => {
            const outputValue =
              OperationHandlerResult.getSuccessfulValueOrFail(output);
            expect(outputValue.result.length).toEqual(19);
          })
          .finallyDoNothing()
      )
      .nothingAfterAll()
);

operation.json


A DDL operation prepares dropdowns for select fields on the properties panel, which means you don't want them to show up as an actual operation on the connector itself.

To mark the operation as a DDL operation, add "type": "ddl" in this file.

Here's the complete operation.json for reference:

Copy
Copied
{
  "name": "list_movie_genres_ddl",
  "title": "ListMovieGenresDdl",
  "description": "",
  "type": "ddl"
}

Test the operation


You can test the operation now by:

tray-cdk connector test [OPERATION_NAME]

In our case, this is tray-cdk connector test list_movie_genres_ddl

You can proceed to the next page, where we will use the DDL operation within another operation.