Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
allprojects {
repositories {
mavenCentral()
}
}
plugins {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes are unnecessary. Revert them.

alias(libs.plugins.com.github.ben.manes.versions)
id("com.bytechef.java-common-conventions")
Expand Down
29 changes: 17 additions & 12 deletions client/public/mockServiceWorker.js
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes are unnecessary. Revert them.

Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
* - Please do NOT modify this file.
*/

const PACKAGE_VERSION = '2.10.5'
const INTEGRITY_CHECKSUM = 'f5825c521429caf22a4dd13b66e243af'
const PACKAGE_VERSION = '2.11.4'
const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82'
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
const activeClientIds = new Set()

Expand Down Expand Up @@ -71,11 +71,6 @@ addEventListener('message', async function (event) {
break
}

case 'MOCK_DEACTIVATE': {
activeClientIds.delete(clientId)
break
}

case 'CLIENT_CLOSED': {
activeClientIds.delete(clientId)

Expand All @@ -94,6 +89,8 @@ addEventListener('message', async function (event) {
})

addEventListener('fetch', function (event) {
const requestInterceptedAt = Date.now()

// Bypass navigation requests.
if (event.request.mode === 'navigate') {
return
Expand All @@ -110,23 +107,29 @@ addEventListener('fetch', function (event) {

// Bypass all requests when there are no active clients.
// Prevents the self-unregistered worked from handling requests
// after it's been deleted (still remains active until the next reload).
// after it's been terminated (still remains active until the next reload).
if (activeClientIds.size === 0) {
return
}

const requestId = crypto.randomUUID()
event.respondWith(handleRequest(event, requestId))
event.respondWith(handleRequest(event, requestId, requestInterceptedAt))
})

/**
* @param {FetchEvent} event
* @param {string} requestId
* @param {number} requestInterceptedAt
*/
async function handleRequest(event, requestId) {
async function handleRequest(event, requestId, requestInterceptedAt) {
const client = await resolveMainClient(event)
const requestCloneForEvents = event.request.clone()
const response = await getResponse(event, client, requestId)
const response = await getResponse(
event,
client,
requestId,
requestInterceptedAt,
)

// Send back the response clone for the "response:*" life-cycle events.
// Ensure MSW is active and ready to handle the message, otherwise
Expand Down Expand Up @@ -202,9 +205,10 @@ async function resolveMainClient(event) {
* @param {FetchEvent} event
* @param {Client | undefined} client
* @param {string} requestId
* @param {number} requestInterceptedAt
* @returns {Promise<Response>}
*/
async function getResponse(event, client, requestId) {
async function getResponse(event, client, requestId, requestInterceptedAt) {
// Clone the request because it might've been already used
// (i.e. its body has been read and sent to the client).
const requestClone = event.request.clone()
Expand Down Expand Up @@ -255,6 +259,7 @@ async function getResponse(event, client, requestId) {
type: 'REQUEST',
payload: {
id: requestId,
interceptedAt: requestInterceptedAt,
...serializedRequest,
},
},
Expand Down
4 changes: 4 additions & 0 deletions server/libs/modules/components/figma/build.gradle.kts
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes are unnecessary. Revert them.

Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
version="1.0"

dependencies {

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.bytechef.component.figma.action.FigmaGetCommentsAction;
import com.bytechef.component.figma.action.FigmaPostCommentAction;
import com.bytechef.component.figma.connection.FigmaConnection;
import com.bytechef.component.figma.trigger.FigmaNewCommentTrigger;

/**
* Provides the base implementation for the REST based component.
Expand All @@ -41,7 +42,7 @@ public abstract class AbstractFigmaComponentHandler implements OpenApiComponentH
.connection(modifyConnection(FigmaConnection.CONNECTION_DEFINITION))
.clusterElements(modifyClusterElements(tool(FigmaGetCommentsAction.ACTION_DEFINITION),
tool(FigmaPostCommentAction.ACTION_DEFINITION)))
.triggers(getTriggers());
.triggers(FigmaNewCommentTrigger.TRIGGER_DEFINITION);

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you can see in the Figma module, we have an openapi.yaml file. This indicates that the component is created using our OpenAPI generator tool.

Since Figma is a generated component, any manual changes will be overwritten the next time the generator is run.

To add trigger in component definition, overrride getTriggers() method in FigmaComponentHandler class.

@Override
public ComponentDefinition getDefinition() {
Expand Down
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although this trigger is currently implemented using polling, the Figma REST API documentation indicates that file comment events can be delivered via webhooks.

Therefore, this trigger could be more efficiently and reliably implemented as a dynamic webhook trigger, which would allow real-time detection of new comments without periodic polling.

To improve this implementation, consider switching from TriggerType.POLLING to TriggerType.DYNAMIC_WEBHOOK, and implement the necessary webhook registration, deregistration, and event handling logic according to Figma's webhook API.

Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright 2025 ByteChef
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.bytechef.component.figma.trigger;

import static com.bytechef.component.definition.ComponentDsl.array;
import static com.bytechef.component.definition.ComponentDsl.object;
import static com.bytechef.component.definition.ComponentDsl.outputSchema;
import static com.bytechef.component.definition.ComponentDsl.string;
import static com.bytechef.component.definition.ComponentDsl.trigger;

import com.bytechef.component.definition.ComponentDsl.ModifiableTriggerDefinition;
import com.bytechef.component.definition.Context.Http;
import com.bytechef.component.definition.Parameters;
import com.bytechef.component.definition.TriggerContext;
import com.bytechef.component.definition.TriggerDefinition.PollOutput;
import com.bytechef.component.definition.TriggerDefinition.TriggerType;
import com.bytechef.component.definition.TypeReference;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Map;

public class FigmaNewCommentTrigger {
public static final ModifiableTriggerDefinition TRIGGER_DEFINITION = trigger("newComment")
.title("New Comment")
.description("Trigger when a new comment is added to a Figma file.")
.type(TriggerType.POLLING)
.output(
outputSchema(
object().properties(
string("file_key").description("Key of the Figma file."),
string("file_name").description("Name of the Figma file."),
array("comment").items(string())
.description("List of comment messages."),
string("comment_id").description("ID of the comment."),
string("parent_id").description("Parent comment ID, if any."),
string("created_at").description("Timestamp when the comment was created."),
string("resolved_at").description("Timestamp when the comment was resolved."),
string("triggered_by").description("User who added the comment."),
string("timestamp").description("Event timestamp."))))
.poll(FigmaNewCommentTrigger::poll);

protected static PollOutput poll(
Parameters inputParameters,
Parameters connectionParameters,
Parameters closureParameters,
TriggerContext triggerContext) {
String fileKey = inputParameters.getRequiredString("file_key");
String token = connectionParameters.getRequiredString("access_token");

Map<String, Object> body = triggerContext
.http(http -> http.get("https://api.figma.com/v1/files/" + fileKey + "/comments"))
.header("Authorization", "Bearer " + token)
.configuration(Http.responseType(Http.ResponseType.JSON))
.execute()
.getBody(new TypeReference<>() {});

List<Map<String, Object>> comments = (List<Map<String, Object>>) body.get("comments");

List<Map<String, Object>> output = comments.stream()
.map(comment -> Map.of(
"file_key", fileKey,
"file_name", body.get("fileName"),
"comment", List.of(comment.get("message")),
"comment_id", comment.get("id"),
"parent_id", comment.get("parent_id"),
"created_at", comment.get("created_at"),
"resolved_at", comment.get("resolved_at"),
"triggered_by", comment.get("user"),
"timestamp", OffsetDateTime.now()
.toString()))
.toList();
return new PollOutput(output, Map.of(), false);
}
}
Loading