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:
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.
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:
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:
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:
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,
{
"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:
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:
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:
if (genresListResponse.status !== 200) {
return OperationHandlerResult.failure(
OperationHandlerError.connectorError("Network call failed")
);
}
DDL operations should always return a response in the format below:
{
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:
const genresList: GenreList[] = genresListResult.genres.map(
(genre: GenreObject) => {
return {
text: genre.name,
value: genre.id,
};
}
);
Here's the complete handler.ts
for reference:
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:
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:
{
"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.