DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Low-Code Development: Leverage low and no code to streamline your workflow so that you can focus on higher priorities.

DZone Security Research: Tell us your top security strategies in 2024, influence our research, and enter for a chance to win $!

Launch your software development career: Dive head first into the SDLC and learn how to build high-quality software and teams.

Open Source Migration Practices and Patterns: Explore key traits of migrating open-source software and its impact on software development.

Related

  • How To Create a Network Graph Using JavaScript
  • Designing Communication Architectures With Microservices
  • CORS Anywhere on Pure NGINX Config
  • Real-Time Communication Protocols: A Developer's Guide With JavaScript

Trending

  • Setting up CI/CD Pipelines: A Step-By-Step Guide
  • How to Configure Custom Metrics in AWS Elastic Beanstalk Using Memory Metrics Example
  • Developer Git Commit Hygiene
  • The AI Revolution: Empowering Developers and Transforming the Tech Industry
  1. DZone
  2. Software Design and Architecture
  3. Integration
  4. Build a Time-Tracking App With ClickUp API Integration Using Openkoda

Build a Time-Tracking App With ClickUp API Integration Using Openkoda

Learn how to create a time-tracking app with ClickUp API Integration using Openkoda and have a fully functional application up and running in a matter of hours.

By 
Martyna Szczepanska user avatar
Martyna Szczepanska
·
Apr. 09, 24 · Tutorial
Like (7)
Save
Tweet
Share
4.9K Views

Join the DZone community and get the full member experience.

Join For Free

Is it possible to build a time-tracking app in just a few hours? It is, and in this article, I'll show you how! 

I’m a senior backend Java developer with 8 years of experience in building web applications. I will show you how satisfying and revolutionary it can be to save a lot of time on building my next one. The approach I use is as follows: I want to create a time-tracking application (I called it Timelog) that integrates with the ClickUp API. It offers a simple functionality that will be very useful here: creating time entries remotely. 

In order to save time, I will use some out-of-the-box functionalities that the Openkoda platform offers. These features are designed with developers in mind. Using them, I can skip building standard features that are used in every web application (over and over again). Instead, I can focus on the core business logic.

I will use the following pre-built features for my application needs:

  • Login/password authentication
  • User and organization management
  • Different user roles and privileges
  • Email sender
  • Logs overview
  • Server-side code editor
  • Web endpoints creator
  • CRUDs generator

Let’s get started!

Timelog Application Overview

Our sample internal application creates a small complex system that can then be easily extended both model-wise and with additional business logic or custom views.

The main focus of the application is to:

  • Store the data required to communicate with the ClickUp API.
  • Assign users to their tickets.
  • Post new time entries to the external API.

To speed up the process of building the application, we relied on some of the out-of-the-box functionalities mentioned above. At this stage, we used the following ones:

  • Data model builder (Form) - Allows us to define data structures without the need to recompile the application, with the ability to adjust the data schema on the fly
  • Ready-to-use management functionalities - With this one, we can forget about developing things like authentication, security, and standard dashboard view.
  • Server-side code editor - Used to develop a dedicated service responsible for ClickUp API integration, it is coded in JavaScript all within the Openkoda UI.
  • WebEndpoint builder - Allows us to create a custom form handler that uses a server-side code service to post time tracking entry data to the ClickUp servers instead of storing it in our internal database

Step 1: Setting Up the Architecture

To implement the functionality described above and to store the required data, we designed a simple data model, consisting of the following five entities.

ClickUpConfig, ClickUpUser, Ticket, and Assignment are designed to store the keys and IDs required for connections and messages sent to the ClickUp API. The last one, TimeEntry, is intended to take advantage of a ready-to-use HTML form (Thymeleaf fragment), saving a lot of time on its development.

The following shows the detailed structure of a prepared data model for the Timelog ClickUp integration.

  • ClickUpConfig 
    • apiKey - ClickUp API key 
    • teamId - ID of space in ClickUp to create time entry in
  • ClickUpUser
    • userId - Internal ID of a User
    • clickUpUserId - ID of a user assigned to a workspace in ClickUp
  • Ticket 
    • name - Internal name of the ticket
    • clickUpTicketid - ID of a ticket in ClickUp to create time entries
  • Assignment 
    • userId - Internal ID of a User
    • ticketId - Internal ID of a Ticket
  • TimeEntry 
    • userId - Internal ID of a User
    • ticketId - Internal ID of a ticket 
    • date - Date of a time entry
    • durationHours - Time entry duration provided in hours
    • durationMinutes - Time entry duration provided in minutes
    • description - Short description for created time entry

We want to end up with five data tiles on the dashboard:

Five tiles on dashboard

Step 2: Integrating With ClickUp API

We integrated our application with the ClickUp API specifically using its endpoint to create time entries in ClickUp.

To connect the Timelog app with our ClickUp workspace, it is required to provide the API Key. This can be done using either a personal API token or a token generated by creating an App in the ClickUp dashboard. For information on how to retrieve one of these, see the official ClickUp documentation.

In order for our application to be able to create time entries in our ClickUp workspace, we need to provide some ClickUp IDs:

  1. teamId: This is the first ID value in the URL after accessing your workspace.
    First ID value in URL after accessing workspace
  2. userId: 
  • To check the user’s ClickUp ID (Member ID), go to Workspace -> Manage Users.
  • On the Users list, select the user’s Settings and then Copy Member ID.
  1. taskId: 

  • Task ID is accessible in three places on the dashboard: URL, task modal, and tasks list view.
  • See the ClickUp Help Center for detailed instructions. 
  • You can recognize the task ID being prefixed by the # sign - we use the ID without the prefix. 

Step 3: Data Model Magic With Openkoda

Openkoda uses the Byte Buddy library to dynamically build entity and repository classes for dynamically registered entities during the runtime of our Spring Boot application.

Here is a short snippet of entity class generation in Openkoda (a whole service class is available on their GitHub).

Java
 
dynamicType = new ByteBuddy()
       .with(SKIP_DEFAULTS)
       .subclass(OpenkodaEntity.class)
       .name(PACKAGE + name)
       .annotateType(entity)
       .annotateType(tableAnnotation)
       .defineConstructor(PUBLIC)
       .intercept(MethodCall
        .invoke(OpenkodaEntity.class.getDeclaredConstructor(Long.class))
               .with((Object) null));


Openkoda provides a custom form builder syntax that defines the structure of an entity. This structure is then used to generate both entity and repository classes, as well as HTML representations of CRUD views such as a paginated table with all records, a settings form, and a simple read-only view.

All of the five entities from the data model described earlier have been registered in the same way, only by using the form builder syntax. 

The form builder snippet for the Ticket entity is presented below.

JavaScript
 
a => a
.text("name")
.text("clickUpTaskId")


The definition above results in having the entity named Ticket with a set of default fields for OpenkodaEntity and two custom ones named “name” and “clickUpTaskId”. 

The database table structure for dynamically generated Ticket entity is as follows:

Markdown
 
                               Table "public.dynamic_ticket"
      Column      |           Type           | Collation | Nullable |        Default
------------------+--------------------------+-----------+----------+-----------------------
 id               | bigint                   |           | not null |
 created_by       | character varying(255)   |           |          |
 created_by_id    | bigint                   |           |          |
 created_on       | timestamp with time zone |           |          | CURRENT_TIMESTAMP
 index_string     | character varying(16300) |           |          | ''::character varying
 modified_by      | character varying(255)   |           |          |
 modified_by_id   | bigint                   |           |          |
 organization_id  | bigint                   |           |          |
 updated_on       | timestamp with time zone |           |          | CURRENT_TIMESTAMP
 click_up_task_id | character varying(255)   |           |          |
 name             | character varying(255)   |           |          |


The last step of a successful entity registration is to refresh the Spring context so it recognizes the new repository beans and for Hibernate to acknowledge entities. It can be done by restarting the application from the Admin Panel (section Monitoring).

Our final result is an auto-generated full CRUD for the Ticket entity.

Auto-generated Ticket settings view:

Openkoda Auto-generated ticket view

Auto-generated all Tickets list view:

Auto-generated all Tickets list view

Step 4: Setting Up Server-Side Code as a Service

OpenKoda Server-Side Code

We implemented ClickUp API integration using the Openkoda Server-Side Code keeping API calls logic separate as a service. It is possible to use the exported JS functions further in the logic of custom form view request handlers.

Then we created a JavaScript service that delivers functions responsible for ClickUp API communication. Openkoda uses GraalVM to run any JS code fully on the backend server. 

Our ClickupAPI server-side code service has only one function (postCreateTimeEntry) which is needed to meet our Timelog application requirements. 

JavaScript
 
export function postCreateTimeEntry(apiKey, teamId, duration, description, date, assignee, taskId) {
  let url = `https://api.clickup.com/api/v2/team/${teamId}/time_entries`;
  let timeEntryReq = {
      duration: duration,
      description: '[Openkoda Timelog] ' + description,
      billable: true,
      start: date,
      assignee: assignee,
      tid: taskId,
  };
  let headers = {Authorization: apiKey};
  return context.services.integrations.restPost(url, timeEntryReq, headers);
}


To use such a service later on in WebEndpoints, it is easy enough to follow the standard JS import expression import * as clickupAPI from 'clickupAPI';.

Step 5: Building Time Entry Form With Custom GET/POST Handlers

Here, we prepare the essential screen for our demo application: the time entry form which posts data to the ClickUp API. All is done in the Openkoda user interface by providing simple HTML content and some JS code snippets.

Openkoda user interface: HTML content, JS snippets

The View

The HTML fragment is as simple as the one posted below. We used a ready-to-use form Thymeleaf fragment (see form tag) and the rest of the code is a standard structure of a Thymeleaf template. 

HTML
 
<!--DEFAULT CONTENT-->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" lang="en" layout:decorate="~{${defaultLayout}}">
<body>
<div class="container">
    <h1 layout:fragment="title"/>
    <div layout:fragment="content">
          <form th:replace="~{generic-forms::generic-form(${TimeEntry}, 'TimeEntry', '', '', '', 'Time Entry', #{template.save}, true)}"></form>
    </div>
</div>
</body>
</html>


HTTP Handlers

Once having a simple HTML code for the view, we need to provide the actual form object required for the generic form fragment (${TimeEntry}). 

Endpoints: GET

We do it inside a GET endpoint as a first step, and after that, we set the currently logged user ID so there’s a default value selected when entering the time entry view.

JavaScript
 
flow
.thenSet("TimeEntry", a => a.services.data.getForm("TimeEntry"))
.then(a => a.model.get("TimeEntry").dto.set("userId", a.model.get("userEntityId")))


Lastly, the POST endpoint is registered to handle the actual POST request sent from the form view (HTML code presented above). It implements the scenario where a user enters the time entry form, provides the data, and then sends the data to the ClickUp server. 

The following POST endpoint JS code: 

  1. Receives the form data.
  2. Reads the additional configurations from the internal database (like API key, team ID, or ClickUp user ID).
  3. Prepares the data to be sent. 
  4. Triggers the clickupAPI service to communicate with the remote API.
JavaScript
 
import * as clickupAPI from 'clickupAPI';

flow
.thenSet("clickUpConfig", a => a.services.data.getRepository("clickupConfig").search( (root, query, cb) => {
            let orgId = a.model.get("organizationEntityId") != null ? a.model.get("organizationEntityId") : -1;
            return cb.or(cb.isNull(root.get("organizationId")), cb.equal(root.get("organizationId"), orgId));
        }).get(0)
    )
.thenSet("clickUpUser", a => a.services.data.getRepository("clickupUser").search( (root, query, cb) => {
            let userId = a.model.get("userEntityId") != null ? a.model.get("userEntityId") : -1;
            return cb.equal(root.get("userId"), userId);
        })
)
.thenSet("ticket", a => a.form.dto.get("ticketId") != null ? a.services.data.getRepository("ticket").findOne(a.form.dto.get("ticketId")) : null)
.then(a => {
    let durationMs = (a.form.dto.get("durationHours") != null ? a.form.dto.get("durationHours") * 3600000 : 0)
        + (a.form.dto.get("durationMinutes") != null ? a.form.dto.get("durationMinutes") * 60000 : 0);
    return clickupAPI.postCreateTimeEntry(
        a.model.get("clickUpConfig").apiKey,
        a.model.get("clickUpConfig").teamId,
        durationMs,
        a.form.dto.get("description"),
        a.form.dto.get("date") != null ? (new Date(a.services.util.toString(a.form.dto.get("date")))).getTime() : Date.now().getTime(),
        a.model.get("clickUpUser").length ? a.model.get("clickUpUser").get(0).clickUpUserId : -1,
        a.model.get("ticket") != null ? a.model.get("ticket").clickUpTaskId : '')
})


Step 6: Our Application Is Ready!

This is it! 

I built a complex application that is capable of storing the data of users, assignments to their tickets, and any properties required for ClickUp API connection. It provides a Time Entry form that covers ticket selection, date, duration, and description inputs of a single time entry and sends the data from the form straight to the integrated API.

Time Entry form

Not to forget about all of the pre-built functionalities available in Openkoda like authentication, user accounts management, logs overview, etc. As a result, the total time to create the Timelog application was only a few hours.

What I have built is just a simple app with one main functionality. But there are many ways to extend it, e.g., by adding new structures to the data model, by developing more of the ClickUp API integration, or by creating more complex screens like the calendar view below.

Calendar view

If you follow almost exactly the same scenario as I presented in this case, you will be able to build any other simple (or not) business application, saving time on repetitive and boring features and focusing on the core business requirements.

I can think of several applications that could be built in the same way, such as a legal document management system, a real estate application, a travel agency system, just to name a few.

As an experienced software engineer, I always enjoy implementing new ideas and seeing the results quickly. In this case, that is all I did. I spent the least amount of time creating a fully functional application tailored to my needs and skipped the monotonous work.

The .zip package with all code and configuration files are available on my GitHub.

HTML Form (document) Integration API integration JavaScript

Opinions expressed by DZone contributors are their own.

Related

  • How To Create a Network Graph Using JavaScript
  • Designing Communication Architectures With Microservices
  • CORS Anywhere on Pure NGINX Config
  • Real-Time Communication Protocols: A Developer's Guide With JavaScript

Partner Resources


Comments

ABOUT US

  • About DZone
  • Send feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: