Building a DDL (Dynamic dropdown list) operation


Through CDK, You can also build Operations that use dropdown lists on the input properties panel of the operation.

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

At the end of this walkthrough, you will have an DDL operation which can later be called within 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 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 with the information on inputs and outputs for the endpoint, you can initiate development.

info

The next 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 does not require any inputs.

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

Copy
Copied
export type ListMovieGenresDdlInput = {};

output.ts


For building the output schema we can use the JSON output from the API Try it step.

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

You need to use a specific type called DDLOperationOutput for a DDL operation. Here is the full Output.ts file for reference:

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

export type ListMovieGenresDdlOutput = DDLOperationOutput<number>;
info

Note thw DDLOperationOutput<number> here. This can be either or , depending on the type of the value field of the DDL option i.e. type of the API value of the field and not the display value you want to show in the props panel.

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 make a call to 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 to 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 is the full 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 is the full 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 is used to prepare dropdowns for select fields on the properties panel which means you don't want them showing up as an actual operation on the connector itself.

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

Here's full 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 use the DDL operation within another operation.