# 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](https://tray.ai/documentation/images/developer/connector-development-kit/developing-connectors/dynamic-dropdown-lists/building-ddl-operation/ddl_end_result.png)

## Prerequisites

***

You have followed the [quickstart](https://tray.ai/documentation/developer/connector-development-kit/overview/quickstarts/token-raw-http) and deployed your first connector.

### Testing 3rd party endpoints

***

You will need the [Movie Genres](https://developer.themoviedb.org/reference/genre-movie-list) endpoint from TMDB API to build the `list_movies_genres_ddl` operation.

Hitting `Try it!` for [Movie Genres](https://developer.themoviedb.org/reference/genre-movie-list) produces an array of Genres with `id` and `name` of each genre object.

![movie-genres-tryit](https://tray.ai/documentation/images/developer/connector-development-kit/developing-connectors/dynamic-dropdown-lists/building-ddl-operation/movie_genres_tryit.png)

## 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](https://tray.ai/documentation/developer/connector-development-kit/overview/quickstarts/token-raw-http).

## 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](https://tray.ai/documentation/images/developer/connector-development-kit/developing-connectors/dynamic-dropdown-lists/building-ddl-operation/cdk_ddl_list_movie_genres_folder.png)

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:

```js
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:

```js
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](https://developer.themoviedb.org/reference/genre-movie-list) endpoint,

```json
{
  "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:

```js
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](https://developer.themoviedb.org/reference/genre-movie-list) endpoint.

Here is how you can do it via [axios](https://axios-http.com/docs/intro):

```js
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:

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

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

```js
{
    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:

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

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

```js
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](https://developer.themoviedb.org/reference/genre-movie-list) gives us 19 results.

We can test this with:

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

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

```js
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:

```json
\{
  "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.
