Before we start...
If you have just stumbled upon my OSD600 series of blog posts, it has been created to document and share my learnings as I progress through my Open Source Development college course.
After completing Hacktoberfest, my classmates and I were challenged to make more substantial Open Source contributions. It was time to move beyond typo fixes and minor code changes and focus on implementing full-fledged features.
If you read my Hacktoberfest Recap post, you might remember that my largest contribution was adding a settings variable to one project's backend. This was mainly because, in the final week of Hacktoberfest, I decided to unassign myself from a more difficult issue that involved implementing a feature in the Java Spring Boot backend of a project called ByteChef.
As a result, when the new course task was announced, I knew I wanted to revisit ByteChef and face my fear of Java!
In this blog post, I'll share how I learned from the mistakes of my previous contribution attempt and successfully contributed two new features to ByteChef.
A bit about ByteChef
ByteChef is an open-source, low-code platform for API integration and workflow automation (currently under active development). It aims to help users easily create and manage workflows across different applications and systems by adding components (each responsible for interacting with a specific service) and defining their relations.
Cool fact: the company is based in Croatia! 🌍
bytechefhq / bytechef
Open-source, low-code, extendable API integration & workflow automation platform. Integrate your organizations or your SaaS product with any third party API
API integration and workflow automation platform
Website - Documentation - Discord - Twitter
UPDATE: ByteChef is under active development. We are in the alpha stage, and some features might be missing or disabled.
What is ByteChef?
ByteChef is an open-source, low-code, extendable API integration and workflow automation platform. ByteChef can help you as:
- An automation solution that allows you to integrate and build automation workflows across your SaaS apps, internal APIs, and databases.
- An embedded solution targeted explicitly for SaaS products, allowing your customers to integrate applications they use with your product.
Key Features
- Intuitive UI Workflow Editor: build and visualize workflows via the UI editor by dragging and dropping components and defining their relations.
- Event-Driven & Scheduled Workflows: automate scheduled and real-time event-driven workflows via a simple trigger definition.
- Multiple flow controls: use the range of various flow controls such as condition, switch, loop, each, parallel, etc.
- Built-In Code…
The 1-st Issue I worked on
I decided to give myself a head start by picking up an issue similar to the one I had previously dropped (Baserow - Get Row action #1643). So, I found an issue related to the same workflow component and reached out to the issue creator, asking for a second chance to contribute.
Comment for #1642
Hello @monikakuster ,
I previously had to unassign myself from the related issue due to some personal circumstances, but I now have more time and resources to tackle adding new actions to the Baserow component. Could you please assign me to this issue? I’m ready to get started and see it through this time.
Thank you!
To be honest, I felt a bit nervous returning to the project, as I was still ashamed for unassigning myself from the previous issue. However, the project maintainer was kind enough to give me another shot.
The Issue I ended up working on: The task was to implement the Delete Row action for the Baserow (Open Source database platform) component, enabling users to remove specific rows from a table in their Baserow database.
Baserow - Delete Row action #1642
Implement the Delete Row action for the Baserow component to enable users to remove specific rows from a table within their Baserow database.
Action properties:
- table id - id of the table from which the row should be deleted
- row id - id of the row that needs to be deleted
Ouput:
- no output
Documentation Reference: https://baserow.io/api-docs
</div>
<div class="gh-btn-container"><a class="gh-btn" href="https://github.com/bytechefhq/bytechef/issues/1642">View on GitHub</a></div>
Contribution Process
Dev Environment Setup
During my first attempt, I struggled to get the project running, even though I closely followed the instructions in the CONTRIBUTING.md (or at least I thought I did), which left me unable to test my draft code. This time, I decided to focus on correctly setting up my development environment from the very beginning.
Here is an overview of the entire setup process, combining the problems I faced during both my initial and current attempts:
The first issue I encountered was an error when cloning the repository: "File name too long" / "Clone succeeded but checkout failed." I resolved this by configuring Git to accept long paths.
Next, I downloaded the compatible version of the Java SDK (21+) and the Java support extension for VS Code. I then started the necessary Docker containers for the dependent infrastructure (Redis, PostgreSQL, and Mailhog). After compiling the codebase, I attempted to start the server but encountered an error with a terrifyingly long stack trace. I thought the issue was related to unresolved dependencies, as, at first glance, the error message seemed to point in that direction. So, I checked the prerequisites and tried rebuilding the project, but the problem persisted. Upon further investigation (of the stack trace that was too long for the LLM to process), I discovered that the issue was related to the server's inability to connect to PostgreSQL. I checked the PostgreSQL Docker container's logs, but everything appeared to be running as expected. Confused, I decided to seek help on the project's Discord. One of the maintainers asked if I was trying to access an external database, and that’s when the realization struck me: something else was likely occupying the port where the PostgreSQL container was supposed to run. Sure enough, I found that my local PostgreSQL server server was already using that port. After terminating the process and restarting the Docker containers, the server started up as expected.
With the development environment finally set up, I was ready to move forward with implementing a feature.
Feature Implementation
As a Java noob, I was intimidated by the project, especially since I had no experience with Gradle or Java Spring Boot.
On my first attempt, I started by locating where the component actions needed to be implemented. Although I found the relevant functions, I quickly realized that this approach wasn't ideal. Without a clear understanding of the project setup, I struggled to make sense of imported custom classes, methods, and constants, and, inevitably, got overwhelmed.
On my second attempt, I took a step back to study the project structure and familiarize myself with the tools used. I also revisited the project's history to see how similar features had been implemented, which helped me understand the workflow. (By this point, more component actions had been implemented, so I had a chance to learn by looking at previous PRs + the docs on adding new components/component actions were updated, making the implementation process clear).
Lesson learned: It's much easier to tune into a project that is further along in its development, as there's more work to reference and learn from.
After studying the project, I began coding by following the instructions in the ByteChef Developer Guide and referencing existing actions for guidance.
I discovered that creating an action starts with the action()
method from the ComponentDsl
package. It initializes an instance of ModifiableActionDefinition
which provides various methods, such as title()
, description()
, properties()
, and perform()
, to define the action’s properties and logic. Once all the properties are defined, the action is registered in the component handler, making it available for use.
For the action I was implementing, no output was required. Its sole purpose was to send an HTTP DELETE
request to a specific API endpoint, using a table ID and a row ID as inputs. I constructed the URL for this request by referring to the Baserow documentation (you need to create an account to see the docs!). To execute the request, I utilized the http()
method provided by the ActionContext
class.
Overall, the project already provided all the necessary building blocks. My main task was to understand how these components worked together and supply the required parameters to define the action correctly. Once I understood the structure and flow, implementing the action became a straightforward and logical process.
BaserowDeleteRowAction.java:
package com.bytechef.component.baserow.action;
import static com.bytechef.component.baserow.constant.BaserowConstants.ROW_ID;
import static com.bytechef.component.baserow.constant.BaserowConstants.TABLE_ID;
import static com.bytechef.component.definition.ComponentDsl.action;
import static com.bytechef.component.definition.ComponentDsl.integer;
import com.bytechef.component.definition.ActionContext;
import com.bytechef.component.definition.ComponentDsl.ModifiableActionDefinition;
import com.bytechef.component.definition.Parameters;
/**
* @author Arina Kolodeznikova
*/
public class BaserowDeleteRowAction {
public static final ModifiableActionDefinition ACTION_DEFINITION = action("deleteRow")
.title("Delete Row")
.description("Deletes the specified row.")
.properties(
integer(TABLE_ID)
.label("Table ID")
.description("ID of the table containing the row to be deleted.")
.required(true),
integer(ROW_ID)
.label("Row ID")
.description("ID of the row to be deleted.")
.required(true))
.perform(BaserowDeleteRowAction::perform);
private BaserowDeleteRowAction() {
}
protected static Object perform(
Parameters inputParameters, Parameters connectionParameters, ActionContext actionContext) {
actionContext
.http(http -> http.delete(
"/database/rows/table/" + inputParameters.getRequiredString(TABLE_ID) + "/" +
inputParameters.getRequiredString(ROW_ID) + "/"))
.execute();
return null;
}
}
Testing
After implementing the action, I tested the row deletion by rebuilding the project, starting the server and client, creating a test table in my Baserow account, setting up the test workflow in the frontend, adding the delete row action for the Baserow component, connecting the component to the service using my Baserow credentials, inputting the table and row IDs, and executing the workflow — and it worked as expected! (For details on creating and testing components, refer to ByteChef's Quick Start Guide).
Creating the Delete Row action:
Workflow successfully running:
The row I specified (row #1) is no longer in the DB:
After confirming that my action worked as expected, I began working on the unit test. I reviewed tests for other Baserow actions and found that they were fairly simple, involving the use of JUnit
and Mockito
for mocking API interactions and input parameters to ensure the correct API calls were made and expected results returned.
Following this approach, I mocked the ActionContext
, Http.Executor
, and Http.Response
to simulate the API. Using MockParametersFactory
, I created mock parameters for the table and row IDs. I then configured the Http.Executor
to return the mocked response and tested the perform()
method, confirming it returned null
as expected for a successful row deletion. (See my unit test here).
After several iterations of formatting the code, running tests, and fixing minor issues, all tests passed, and my changes were ready for a PR. I went ahead and submitted a Pull Request, and it was merged the following day!
1642 - baserow delete row action #1743
Implemented the Delete Row action for the Baserow component to enable users to remove specific rows from a table within their Baserow database.
Fixes #1642
- New feature (non-breaking change which adds functionality)
- Added a new unit test and executed it alongside the existing test in the module to ensure all tests pass successfully.
- Manually tested the action by creating and executing a test workflow on the client side.
Created a test workflow that included a Baserow delete row action. Configured the workflow by adding the authentication token to connections and specifying the table ID and row ID for the testing Baserow table:
Checked the specified Baserow database table to confirm that the specified row was successfully deleted.:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- Referenced the code for other Baserow component actions
- [ ] I have made corresponding changes to the documentation
- Running
gradlew generateDocumentation
for the module did not produce any documentation updates
- Running
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my feature works
- [x] New and existing unit tests pass locally with my changes
The 2-nd issue I worked on
After my first PR was merged, I was curious to implement an action for another component to explore how the backend integrates interactions with other services. I checked the open issues and found one related to adding an action for the Google Drive component.
Google Drive - Get File action #1647
The Get File action for the Google Drive component is designed to retrieve a specified file from the user's Google Drive. This action allows users to access file metadata and content.
Action properties:
- file ID - the ID of the file you wish to retrieve
Ouput:
- file - a file object containing detailed information about the retrieved file.
Documentation Reference: https://developers.google.com/drive/api/reference/rest/v3/files/get
I left a comment asking to be assigned the issue and also requesting feedback on possible areas for improvement in my previous PR.
Although I didn't receive any feedback, the issue was assigned to me a couple of days later. At first, I was a bit surprised by the delay in responses (mainly because the maintainers had been super quick to reply and assign issues before), but after a quick Google search, I learned that there was a long weekend in Croatia, where most of the project maintainers are based.
Note: The ability to put yourself in someone else's shoes is especially important when working with people from all around the world. As for me, I certainly wouldn't want to be bothered during the holidays.
Contribution Process
Implementing the feature
I was already familiar with the project, so writing the second feature was much easier. The main difference between writing the action for Baserow and Google Drive was that it used different custom classes. So, this time, I essentially needed to familiarize myself with a few new "building blocks", the key one being the Google API SDK library for Java.
I read the documentation reference linked by the maintainer and realized that I saw the same method in the Google Drive Copy File action. By extracting the necessary components from the existing logic, I was able to write an action that returns the metadata for the selected file in the linked Google Drive (See the code on GitHub).
Testing the feature
Manually testing the feature was fun, as I got to set up a Google Cloud account and OAuth2 credentials to access my Google Drive from the frontend application. (I followed the instructions for the Google Drive component in the docs to generate the necessary credentials).
Retrieving the metadata for one of my Google Drive documents:
After manually testing the feature in the frontend, I created a unit test in the same way as before, using JUnit and Mockito (GoogleDriveGetFileActionTest.java).
Then, I ran the code formatter, ensured that my code passed all the checks, and submitted a Pull Request, asking a few questions about the expected output in the comments. (However, as of writing this blog post, the maintainers haven’t yet responded).
1647 - Added Google Drive Get File Action #1753
Added Get File action to the Google Drive component.
Fixes #1647
- New feature (non-breaking change which adds functionality)
- This change requires a documentation update
- Added a new unit test and executed it alongside the existing test in the module to ensure all tests pass successfully.
- Manually tested the action by creating and executing a test workflow on the client side.
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- Referenced the code for other Google Drive component actions
- [ ] I have made corresponding changes to the documentation
- Running
gradlew generateDocumentation
for the module did not produce any documentation updates
- Running
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my feature works
- [x] New and existing unit tests pass locally with my changes
Afterthoughts
Contributing to ByteChef was a valuable learning experience. I got to author new features and unit tests, learned the importance of taking time to understand the project structure and leveraging existing code as a reference for implementing new features. + I gained hands-on experience with new tools and libraries, including Gradle, Baserow, the Google Drive API SDK, JUnit, and Mockito.
Top comments (0)