Làm thế nào để tham gia phát triền n8n-nodes-chatwork?

Ở bài viết Giới thiệu “n8n-nodes-chatwork” mình đã nói qua về n8n-nodes-chatwork, và cũng nói sẽ có bài viết hướng dẫn tham gia phát triển node này. Đây là bài viết đó.

Bài viết này có nội dung đi sâu vào chi tiết của việc phát triển các tính năng cho chatwork node.  Bài viết tập trung vào hướng dẫn phát triền một “chương trình”, nhưng bên cạnh đó bài viết cũng mang lại một số thông tin, cách viết unit test, quy trình phát triển TDD (Test Driven Development)…

Để có thể thực hành theo nội dung trong bài viết, yêu cầu bạn có hiểu biết cơ bản những thứ sau:

  • Nodejs, npm và cách phát triền ứng dụng sử dụng Nodejs
  • Typescript
  • Git, Github

Chuẩn bị môi trường phát triển

Yêu cầu máy tính bạn cài đặt sẵn Nodejs và npm. Mình đang sử dụng Nodejs v10.19.0, npm v6.14.7. Hãy sử dụng một IDE hoặc Editor mà bạn yêu thích, với mình đó là VSCode.

Sao chép thư mục mã nguồn

Mã nguồn của dự án được public tại link: n8n-nodes-chatwork.

Các bạn clone dự án về máy tính:

git clone git@github.com:hoangsetup/n8n-nodes-chatwork.git

Chuẩn bị môi trường phát triển

Mở dự án bằng IDE/Editor của bạn, mình sử dụng vscode nên có thể dùng lệnh sau để mở dự án:

cd n8n-nodes-chatwork

code .

Tiếp theo, các bạn các gói thư viện mà dự án sử dụng:

npm install

Tới đây, về cơ bản bạn đã có đủ điều kiện để phát triển dự án. Với việc phải viết unit test và cài đặt chỉ số coverage là 100% ở tất cả các mục, có thể đảm bảo tính năng mà bạn phát triển có thể hoạt động đúng logic

coverageThreshold: {
  global: {
    branches: 100,
    functions: 100,
    lines: 100,
    statements: 100
  }
},

Nhưng n8n node là một thứ giúp bạn làm gì đó một cách trực quan, có nghĩa là có phần giao diện để giao tiếp với người. Vấn đề này unit test không thể “cover” được, chúng ta cần 1 thứ gì đó kiểu như E2E (End to End testing). Hiện tại dự án chưa cài đặt e2e test, nên tốt nhất là test “bằng tay”, chúng ta tự kiểm tra chúng khi phát triển.

Tích hợp chatwork node với n8n ở môi trường local

Bước này giúp bạn có thể xem được những thay đổi mà bạn tạo ra. Ở bước này bạn cần có API key của chatwork, vì chatwork node yêu cầu có api key mới có thể hoạt động được.

Build dự án, dự án được viết bằng Typescript, nên chúng ta cần build mã TS thành mã JS trước, bước này cũng chạy gulp script để copy các file resource cần thiết (icons):

npm run build

Nhưng trong tài liệu của n8n, nếu bạn muốn cài đặt một custom node bạn cần cài đặt node đó cùng chỗ với gói n8n. Như vậy bạn cần publish gói n8n-nodes-chatwork lên npm, sau đó gọi lệnh npm install n8n-nodes-chatwork để thử. Như vậy là quá phức tạp cho quy trình phát triển. Rất may, npm hỗ trợ chúng ta “Publish” cái gói ở môi trường local, và có thể sử dụng các gói đó ở các dự án khác cùng môi trường.

Ở dự án này, n8n là một “devDependencies” và đã được cài đặt ở bước trên, giờ chúng ta chỉ cần cài đặt gói n8n-nodes-chatwork nữa là đủ.

“Publish” gói n8n-nodes-chatwork ở máy local:

npm link

Cài đặt gói n8n-nodes-chatwork:

npm link n8n-nodes-chatwork

Thông tin tham khảo cho npm link

Chạy thử:

npm run n8n

Với cấu hình mặc định, bạn có thể truy cập vào link http://localhost:5678/ , nhấn thêm mới một node, lọc với từ khóa “chatwork”, bạn phải thấy chatwork node ở đó:

Chọn chatwork node, khởi tạo Chatwork credential, và chạy thử một “lệnh”:

Tới đây thì có thể chuyển sang bước phát triển được rồi.

Test Driven Development – TDD

Nếu bạn không theo phong cách này, hoặc chưa quen với TDD thì cũng không sao vì dự án đã cài đặt mức độ coverage tối thiểu là 100%. Các bạn có thể viết code trước, sau đó mới viết unit test cho đoạn code đó cũng không vấn đề gì. Lời khuyên của mình là các bạn nên tìm hiểu về TDD, nếu được thì hãy thực hành nó ngay hôm nay.

test-driven-development-tdd
https://medium.com/@luisfmachado/swift-test-driven-development-tdd-810add46a1b9

Cách để thêm mới một tính năng

Các bạn có thể tìm lỗi hoặc cải tiến dự án, nhưng ở bài viết này mình sẽ lấy ví dụ về việc thêm một tính năng mới cho dự án.

Các tính năng chưa dược thực hiện được liệt kê ở link: https://github.com/hoangsetup/n8n-nodes-chatwork/issues.

Các bạn cũng có thể tự thêm những tính năng chưa được liệt kê ở danh sách trên.

Để xác nhận sẽ phát triển một tính năng, hãy comment vào issue tương ứng, thể hiện rằng bạn sẽ/đang cố thực hiện nó. Hãy đảm bảo không có ai đang thực hiện tính năng mà bạn đang muốn thực hiện.

Bài viết này sẽ lấy issue https://github.com/hoangsetup/n8n-nodes-chatwork/issues/27 này làm ví dụ. Về cơ bản task này thực hiện api GET /rooms/{room_id}/tasks, lấy thông tin các task của một room theo room_id.

Thông thường sẽ có 2 file chúng ta quan tâm:

  • Chatwork.node.ts: File logic chính của node
  • Chatwork.node.spec.ts: File unit test cho file trên

Bắt đầu từ file spec trước, chúng ta sẽ thêm mới một spec cho tính năng mới. Vì chúng ta sẽ cập nhật vào hàm execute, nên bạn sẽ thấy có rất nhiều hàm sẽ được mock, chúng ta chỉ quan tâm tới việc api có được gọi đúng với các tham số hay không.

diff --git a/src/nodes/Chatwork/Chatwork.node.spec.ts b/src/nodes/Chatwork/Chatwork.node.spec.ts
index f9b80eb..e71710f 100644
--- a/src/nodes/Chatwork/Chatwork.node.spec.ts
+++ b/src/nodes/Chatwork/Chatwork.node.spec.ts
@@ -23,6 +23,7 @@ enum Operations {
   updateInfo = 'updateInfo',
   getMessageDetail = 'getMessageDetail',
   deleteMessage = 'deleteMessage',
+  getRoomTasks = 'getRoomTasks',
 }
 
 interface ITestCase01 {
@@ -381,5 +382,41 @@ describe('Chatwork', () => {
       expect(chatworkApiRequestMock).toBeCalledTimes(2);
       expect(chatworkApiRequestMock).toHaveBeenCalledWith('POST', '/rooms/1/messages', body);
     })
+
+    test('should call get tasks api when operation = "getRoomTasks"', async () => {
+      const apiResponse = [
+        {
+          'task_id': 3,
+          'account': {
+            'account_id': 101,
+            'name': 'Bob',
+            'avatar_image_url': 'https://example.com/abc.png',
+          },
+        },
+      ];
+      const getResourceMock = getNodeParameterMock.mockReturnValueOnce(Resources.rooms);
+      const getOperationMock = getNodeParameterMock.mockReturnValueOnce(Operations.getRoomTasks);
+
+      const roomId = 1;
+
+      const getDefaultRoomIdMock = getNodeParameterMock.mockReturnValueOnce(roomId);
+      const getRoomIdMock = getNodeParameterMock.mockReturnValueOnce(roomId);
+
+      getInputDataMock.mockReturnValue([{}]);
+      chatworkApiRequestMock.mockResolvedValueOnce(apiResponse);
+      returnJsonArrayMock.mockImplementationOnce((items: any[]) => {
+        return items.map((i) => ({ json: i }));
+      });
+
+      const result = await chatworkNode.execute.call(context as any);
+
+      expect(getResourceMock).toHaveBeenCalledWith('resource', 0);
+      expect(getOperationMock).toHaveBeenCalledWith('operation', 0);
+      expect(getDefaultRoomIdMock).toHaveBeenCalledWith('roomId', 0);
+      expect(getRoomIdMock).toHaveBeenCalledWith('roomId', 0);
+
+      expect(chatworkApiRequestMock).toHaveBeenCalledWith('GET', `/rooms/${roomId}/tasks`, null);
+      expect([[{ json: apiResponse[0] }]]).toEqual(result);
+    })
   })
 });

 

Chúng ta giả lập giá trị trả về của các “unit” khác, ví dụ getOperationMock sẽ trả về “getRoomTasks“, từ đó mong muốn api sẽ được gọi với tham số:

expect(chatworkApiRequestMock).toHaveBeenCalledWith('GET', `/rooms/${roomId}/tasks`);

Run test, trong lúc phát triển chúng ta sẽ thêm lệnh –watch để lắng nghe thay đổi nội dung file và chạy lại test:

npm run test -- --watch

Chúng ta nhận được “Mã Đỏ”:

      ✕ should call get tasks api when operation = "getRoomTasks" (1 ms)

  ● Chatwork › execute › should call get tasks api when operation = "getRoomTasks"

    getRoomTasks is not supported.

      455 |                 break;
      456 |               default:
    > 457 |                 throw new Error(`${operation} is not supported.`)
          |                       ^
      458 |             }
      459 |           }
      460 |         }

      at Object.execute (src/nodes/Chatwork/Chatwork.node.ts:457:23)
      at Object.test (src/nodes/Chatwork/Chatwork.node.spec.ts:408:49)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 17 passed, 18 total
Snapshots:   0 total
Time:        3.948 s

Nội dung chính của lỗi là “getRoomTasks is not supported.”. Khi chúng ta đã có “Mã đỏ” chúng ta “được phép” cập nhật production code – Chatwork.node.ts

diff --git a/src/nodes/Chatwork/Chatwork.node.ts b/src/nodes/Chatwork/Chatwork.node.ts
index d9a2b98..687f6ed 100644
--- a/src/nodes/Chatwork/Chatwork.node.ts
+++ b/src/nodes/Chatwork/Chatwork.node.ts
@@ -453,6 +453,9 @@ export class Chatwork implements INodeType {
                 method = 'DELETE';
                 endpoint += `/messages/${messageId}`;
                 break;
+              case 'getRoomTasks':
+                endpoint += '/tasks';
+                break;
               default:
                 throw new Error(`${operation} is not supported.`)
             }

Khá đơn giản, method mặc định là GET – không cần thay đổi, endpoint thêm chuỗi /tasks.

Lưu file, test được chạy lại, và chúng ta nhận được “Mã xanh”:

Test Suites: 1 passed, 1 total
Tests:       18 passed, 18 total
Snapshots:   0 total
Time:        3.929 s

Tới bước này, chúng ta có thể xem xét để tối ưu hóa production code hoặc test code. Nhưng ở đây không có gì để refactor cả :D. Chúng ta đã hoàn thành! Chưa đâu.

Thử lại trên giao diện, chúng ta không có cách nào để chọn operator “getRoomTasks”.

Chúng ta phải thêm operator là “getRoomTasks” cho resource “Rooms”. Ở bước này bạn có thể terminate lệnh “npm run n8n” trước đó đi, thay vào đó bằng lệnh:

npm run dev

Lệnh này sẽ lắng nghe sự thay đổi các file trong dự án, nếu có thay đổi sẽ tiến hành build lại code, chạy lại n8n để cập nhật code mới nhất.

Thêm mới operator cho Rooms resource

Vẫn là file Chatwork.node.ts, vì phần này cấu hình giao diện của node, nên chúng ta không viết test cho chúng:

diff --git a/src/nodes/Chatwork/Chatwork.node.ts b/src/nodes/Chatwork/Chatwork.node.ts
index 687f6ed..3481c9a 100644
--- a/src/nodes/Chatwork/Chatwork.node.ts
+++ b/src/nodes/Chatwork/Chatwork.node.ts
@@ -257,6 +257,11 @@ export class Chatwork implements INodeType {
             value: 'deleteMessage',
             description: 'Delete the specified message',
           },
+          {
+            name: 'Get tasks',
+            value: 'getRoomTasks',
+            description: 'Get the list of tasks associated with the specified chat',
+          },
         ],
         default: 'get',
       },
@@ -277,6 +282,7 @@ export class Chatwork implements INodeType {
               'updateInfo',
               'getMessageDetail',
               'deleteMessage',
+              'getRoomTasks',
             ],
           },
         },

Lưu lại và thử lại với giao diện:

Đã xong!

Commit và tạo Pull Request

Mọi thứ đã ổn, chạy lại lint và test có kiểm tra coverage một lần nữa trước khi tạo commit

npm run lint && npm run test:coverage
> jest --passWithNoTests "--coverage"

 PASS  src/shared/GenericFunctions.spec.ts
 PASS  src/nodes/Chatwork/Chatwork.node.spec.ts
----------------------|---------|----------|---------|---------|-------------------
File                  | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------------------|---------|----------|---------|---------|-------------------
All files             |     100 |      100 |     100 |     100 |                   
 nodes/Chatwork       |     100 |      100 |     100 |     100 |                   
  Chatwork.node.ts    |     100 |      100 |     100 |     100 |                   
 shared               |     100 |      100 |     100 |     100 |                   
  Constants.ts        |     100 |      100 |     100 |     100 |                   
  GenericFunctions.ts |     100 |      100 |     100 |     100 |                   
----------------------|---------|----------|---------|---------|-------------------

Test Suites: 2 passed, 2 total
Tests:       24 passed, 24 total
Snapshots:   0 total
Time:        4.736 s
Ran all test suites.

Tạo commit và đẩy lên remote tạo PR:

git commit -m "Add operation for GET /rooms/{room_id}/tasks api" && git push origin head

PR link: https://github.com/hoangsetup/n8n-nodes-chatwork/pull/33

Khi PR được tạo, Github Action sẽ tự động chạy một số task, như lint, test:coverage một lần nữa:

Github Action bot sẽ tạo một comment cho PR để thông báo về kết quả test coverage, như thế này:

Khi PR này được merge và publish lên npm là mọi có thể sử dụng được tính năng đó.

Tổng kết

Bài viết đi qua sơ lược về việc làm thế nào để tham gia phát triển dự án, có rất nhiều phần không được đi sâu vào, ví dụ: Logic của n8n node, “cú pháp” của n8n… Các bạn có thể tự tìm hiểu chúng.

Bài viết mang lại một số từ khóa có thể hữu ích với các bạn như: TDD, unit test, Typescript, Jest, Github Action…

Các bạn có thể không quan tâm tới n8n-nodes-chatwork, nhưng dự án cũng là một ví dụ để bạn setup dự án với Typescript, unit test, mocking với Jest…

Hẹn các bạn ở bài viết sau!