diff --git a/.github/pull_request_title_conventions.md b/.github/pull_request_title_conventions.md index 8808000e3b330..0fc951d0e4475 100644 --- a/.github/pull_request_title_conventions.md +++ b/.github/pull_request_title_conventions.md @@ -11,19 +11,19 @@ A PR title consists of these elements: | | Capitalized | | No period at the end. │ │ - │ └─⫸ Scope: API|core|editor|* Node + │ └─⫸ Scope: API|core|editor|* Node|benchmark │ └─⫸ Type: build|ci|docs|feat|fix|perf|refactor|test ``` - PR title - - type - - scope (*optional*) - - summary + - type + - scope (_optional_) + - summary - PR description - - body (optional) - - blank line - - footer (optional) + - body (optional) + - blank line + - footer (optional) The structure looks like this: @@ -46,13 +46,14 @@ If the prefix is `feat`, `fix` or `perf`, it will appear in the changelog. H The scope should specify the place of the commit change as long as the commit clearly addresses one of the following supported scopes. (Otherwise, omit the scope!) -- `API` - changes to the *public* API +- `API` - changes to the _public_ API - `core` - changes to the core / private API / backend of n8n - `editor` - changes to the Editor UI - `* Node` - changes to a specific node or trigger node (”`*`” to be replaced with the node name, not its display name), e.g. - - mattermost → Mattermost Node - - microsoftToDo → Microsoft To Do Node - - n8n → n8n Node + - mattermost → Mattermost Node + - microsoftToDo → Microsoft To Do Node + - n8n → n8n Node +- `benchmark` - changes to the Benchmark cli ### **Summary** @@ -60,8 +61,8 @@ The summary contains succinct description of the change: - use the imperative, present tense: "change" not "changed" nor "changes" - capitalize the first letter -- *no* dot (.) at the end -- do *not* include Linear ticket IDs etc. (e.g. N8N-1234) +- _no_ dot (.) at the end +- do _not_ include Linear ticket IDs etc. (e.g. N8N-1234) - suffix with “(no-changelog)” for commits / PRs that should not get mentioned in the changelog. ### **Body (optional)** @@ -95,7 +96,7 @@ Closes # A Breaking Change section should start with the phrase "`BREAKING CHANGE:` " followed by a summary of the breaking change, a blank line, and a detailed description of the breaking change that also includes migration instructions. > 💡 A breaking change can additionally also be marked by adding a “`!`” to the header, right before the “`:`”, e.g. `feat(editor)!: Remove support for dark mode` -> +> > This makes locating breaking changes easier when just skimming through commit messages. > 💡 The breaking changes must also be added to the [packages/cli/BREAKING-CHANGES.md](https://github.com/n8n-io/n8n/blob/master/packages/cli/BREAKING-CHANGES.md) file located in the n8n repository. @@ -109,4 +110,4 @@ If the commit reverts a previous commit, it should begin with `revert:` , foll The content of the commit message body should contain: - information about the SHA of the commit being reverted in the following format: `This reverts commit `, -- a clear description of the reason for reverting the commit message. \ No newline at end of file +- a clear description of the reason for reverting the commit message. diff --git a/.github/workflows/benchmark-destroy-nightly.yml b/.github/workflows/benchmark-destroy-nightly.yml new file mode 100644 index 0000000000000..f77e305571a30 --- /dev/null +++ b/.github/workflows/benchmark-destroy-nightly.yml @@ -0,0 +1,40 @@ +name: Destroy Benchmark Env + +on: + schedule: + - cron: '30 4 * * *' + workflow_dispatch: + +permissions: + id-token: write + contents: read + +jobs: + build: + runs-on: ubuntu-latest + environment: benchmarking + + steps: + - name: Checkout + uses: actions/checkout@v4.1.1 + + - name: Azure login + uses: azure/login@v2.1.1 + with: + client-id: ${{ secrets.BENCHMARK_ARM_CLIENT_ID }} + tenant-id: ${{ secrets.BENCHMARK_ARM_TENANT_ID }} + subscription-id: ${{ secrets.BENCHMARK_ARM_SUBSCRIPTION_ID }} + + - run: Setup node + - uses: actions/setup-node@v4.0.2 + with: + node-version: 20.x + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Destroy cloud env + if: github.event.inputs.debug == 'true' + run: pnpm destroy-cloud-env + working-directory: packages/@n8n/benchmark diff --git a/.github/workflows/benchmark-nightly.yml b/.github/workflows/benchmark-nightly.yml new file mode 100644 index 0000000000000..eb5aacbd0f81b --- /dev/null +++ b/.github/workflows/benchmark-nightly.yml @@ -0,0 +1,69 @@ +name: Run Nightly Benchmark +run-name: Benchmark ${{ inputs.n8n_tag }} + +on: + schedule: + - cron: '0 2 * * *' + workflow_dispatch: + inputs: + debug: + description: 'Use debug logging' + required: true + default: 'false' + n8n_tag: + description: 'Name of the n8n docker tag to run the benchmark against.' + required: true + default: 'nightly' + benchmark_tag: + description: 'Name of the benchmark cli docker tag to run the benchmark with.' + required: true + default: 'latest' + +env: + ARM_CLIENT_ID: ${{ secrets.BENCHMARK_ARM_CLIENT_ID }} + ARM_SUBSCRIPTION_ID: ${{ secrets.BENCHMARK_ARM_SUBSCRIPTION_ID }} + ARM_TENANT_ID: ${{ secrets.BENCHMARK_ARM_TENANT_ID }} + K6_API_TOKEN: ${{ secrets.K6_API_TOKEN }} + +permissions: + id-token: write + contents: read + +jobs: + build: + runs-on: ubuntu-latest + environment: benchmarking + + steps: + - name: Checkout + uses: actions/checkout@v4.1.1 + + - uses: hashicorp/setup-terraform@v3 + with: + terraform_version: '1.8.5' + + - run: corepack enable + - uses: actions/setup-node@v4.0.2 + with: + node-version: 20.x + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Azure login + uses: azure/login@v2.1.1 + with: + client-id: ${{ env.ARM_CLIENT_ID }} + tenant-id: ${{ env.ARM_TENANT_ID }} + subscription-id: ${{ env.ARM_SUBSCRIPTION_ID }} + + - name: Run the benchmark with debug logging + if: github.event.inputs.debug == 'true' + run: pnpm run-in-cloud sqlite --debug + working-directory: packages/@n8n/benchmark + + - name: Run the benchmark + if: github.event.inputs.debug != 'true' + run: pnpm run-in-cloud sqlite + working-directory: packages/@n8n/benchmark diff --git a/.github/workflows/docker-images-benchmark.yml b/.github/workflows/docker-images-benchmark.yml index cf9d7359a5fe2..b0aa6e997decc 100644 --- a/.github/workflows/docker-images-benchmark.yml +++ b/.github/workflows/docker-images-benchmark.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: push: branches: - - main + - master paths: - 'packages/@n8n/benchmark/**' - 'pnpm-lock.yaml' diff --git a/packages/@n8n/benchmark/.eslintrc.js b/packages/@n8n/benchmark/.eslintrc.js index 99c277f212ac9..463a7d2f52599 100644 --- a/packages/@n8n/benchmark/.eslintrc.js +++ b/packages/@n8n/benchmark/.eslintrc.js @@ -12,7 +12,7 @@ module.exports = { project: './tsconfig.json', }, - ignorePatterns: ['scenarios/**'], + ignorePatterns: ['scenarios/**', 'scripts/**'], rules: { 'n8n-local-rules/no-plain-errors': 'off', diff --git a/packages/@n8n/benchmark/.gitignore b/packages/@n8n/benchmark/.gitignore new file mode 100644 index 0000000000000..ee041668579c9 --- /dev/null +++ b/packages/@n8n/benchmark/.gitignore @@ -0,0 +1,4 @@ +**/.terraform/* +**/*.tfstate* +**/*.tfvars +privatekey.pem diff --git a/packages/@n8n/benchmark/README.md b/packages/@n8n/benchmark/README.md index 569bcf897febd..a16e03572a393 100644 --- a/packages/@n8n/benchmark/README.md +++ b/packages/@n8n/benchmark/README.md @@ -40,6 +40,14 @@ N8N_USER_EMAIL=user@n8n.io N8N_USER_PASSWORD=password ./bin/n8n-benchmark run K6_PATH=/opt/homebrew/bin/k6 N8N_USER_EMAIL=user@n8n.io N8N_USER_PASSWORD=password ./bin/n8n-benchmark run ``` +## Running in the cloud + +There's a script to run the performance tests in a cloud environment. The script provisions a cloud environment, sets up n8n in the environment, runs the tests and destroys the environment. + +```sh +pnpm run-in-cloud +``` + ## Configuration The configuration options the cli accepts can be seen from [config.ts](./src/config/config.ts) diff --git a/packages/@n8n/benchmark/infra/.terraform.lock.hcl b/packages/@n8n/benchmark/infra/.terraform.lock.hcl new file mode 100644 index 0000000000000..30125168613f2 --- /dev/null +++ b/packages/@n8n/benchmark/infra/.terraform.lock.hcl @@ -0,0 +1,60 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/azurerm" { + version = "3.115.0" + constraints = "~> 3.115.0" + hashes = [ + "h1:O7C3Xb+MSOc9C/eAJ5C/CiJ4vuvUsYxxIzr9ZurmHNI=", + "zh:0ea93abd53cb872691bad6d5625bda88b5d9619ea813c208b36e0ee236308589", + "zh:26703cb9c2c38bc43e97bc83af03559d065750856ea85834b71fbcb2ef9d935c", + "zh:316255a3391c49fe9bd7c5b6aa53b56dd490e1083d19b722e7b8f956a2dfe004", + "zh:431637ae90c592126fb1ec813fee6390604275438a0d5e15904c65b0a6a0f826", + "zh:4cee0fa2e84f89853723c0bc72b7debf8ea2ffffc7ae34ff28d8a69269d3a879", + "zh:64a3a3c78ea877515365ed336bd0f3abbe71db7c99b3d2837915fbca168d429c", + "zh:7380d7b503b5a87fd71a31360c3eeab504f78e4f314824e3ceda724d9dc74cf0", + "zh:974213e05708037a6d2d8c58cc84981819138f44fe40e344034eb80e16ca6012", + "zh:9a91614de0476074e9c62bbf08d3bb9c64adbd1d3a4a2b5a3e8e41d9d6d5672f", + "zh:a438471c85b8788ab21bdef4cd5ca391a46cbae33bd0262668a80f5e6c4610e1", + "zh:bf823f2c941b336a1208f015466212b1a8fdf6da28abacf59bea708377709d9e", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.6.2" + hashes = [ + "h1:VavG5unYCa3SYISMKF9pzc3718M0bhPlcbUZZGl7wuo=", + "zh:0ef01a4f81147b32c1bea3429974d4d104bbc4be2ba3cfa667031a8183ef88ec", + "zh:1bcd2d8161e89e39886119965ef0f37fcce2da9c1aca34263dd3002ba05fcb53", + "zh:37c75d15e9514556a5f4ed02e1548aaa95c0ecd6ff9af1119ac905144c70c114", + "zh:4210550a767226976bc7e57d988b9ce48f4411fa8a60cd74a6b246baf7589dad", + "zh:562007382520cd4baa7320f35e1370ffe84e46ed4e2071fdc7e4b1a9b1f8ae9b", + "zh:5efb9da90f665e43f22c2e13e0ce48e86cae2d960aaf1abf721b497f32025916", + "zh:6f71257a6b1218d02a573fc9bff0657410404fb2ef23bc66ae8cd968f98d5ff6", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:9647e18f221380a85f2f0ab387c68fdafd58af6193a932417299cdcae4710150", + "zh:bb6297ce412c3c2fa9fec726114e5e0508dd2638cad6a0cb433194930c97a544", + "zh:f83e925ed73ff8a5ef6e3608ad9225baa5376446349572c2449c0c0b3cf184b7", + "zh:fbef0781cb64de76b1df1ca11078aecba7800d82fd4a956302734999cfd9a4af", + ] +} + +provider "registry.terraform.io/hashicorp/tls" { + version = "4.0.5" + hashes = [ + "h1:zeG5RmggBZW/8JWIVrdaeSJa0OG62uFX5HY1eE8SjzY=", + "zh:01cfb11cb74654c003f6d4e32bbef8f5969ee2856394a96d127da4949c65153e", + "zh:0472ea1574026aa1e8ca82bb6df2c40cd0478e9336b7a8a64e652119a2fa4f32", + "zh:1a8ddba2b1550c5d02003ea5d6cdda2eef6870ece86c5619f33edd699c9dc14b", + "zh:1e3bb505c000adb12cdf60af5b08f0ed68bc3955b0d4d4a126db5ca4d429eb4a", + "zh:6636401b2463c25e03e68a6b786acf91a311c78444b1dc4f97c539f9f78de22a", + "zh:76858f9d8b460e7b2a338c477671d07286b0d287fd2d2e3214030ae8f61dd56e", + "zh:a13b69fb43cb8746793b3069c4d897bb18f454290b496f19d03c3387d1c9a2dc", + "zh:a90ca81bb9bb509063b736842250ecff0f886a91baae8de65c8430168001dad9", + "zh:c4de401395936e41234f1956ebadbd2ed9f414e6908f27d578614aaa529870d4", + "zh:c657e121af8fde19964482997f0de2d5173217274f6997e16389e7707ed8ece8", + "zh:d68b07a67fbd604c38ec9733069fbf23441436fecf554de6c75c032f82e1ef19", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} diff --git a/packages/@n8n/benchmark/infra/benchmark-env.tf b/packages/@n8n/benchmark/infra/benchmark-env.tf new file mode 100644 index 0000000000000..eff8fa12a93d1 --- /dev/null +++ b/packages/@n8n/benchmark/infra/benchmark-env.tf @@ -0,0 +1,54 @@ + +data "azurerm_resource_group" "main" { + name = var.resource_group_name +} + +# Random prefix for the resources +resource "random_string" "prefix" { + length = 8 + special = false +} + +# SSH key pair +resource "tls_private_key" "ssh_key" { + algorithm = "RSA" + rsa_bits = 4096 +} + +# Dedicated Host Group & Hosts + +resource "azurerm_dedicated_host_group" "main" { + name = "${random_string.prefix.result}-hostgroup" + location = var.location + resource_group_name = data.azurerm_resource_group.main.name + platform_fault_domain_count = 1 + automatic_placement_enabled = false + zone = 1 + + tags = local.common_tags +} + +resource "azurerm_dedicated_host" "hosts" { + name = "${random_string.prefix.result}-host" + location = var.location + dedicated_host_group_id = azurerm_dedicated_host_group.main.id + sku_name = var.host_size_family + platform_fault_domain = 0 + + tags = local.common_tags +} + +# VM + +module "test_vm" { + source = "./modules/benchmark-vm" + + location = var.location + resource_group_name = data.azurerm_resource_group.main.name + prefix = random_string.prefix.result + dedicated_host_id = azurerm_dedicated_host.hosts.id + ssh_public_key = tls_private_key.ssh_key.public_key_openssh + vm_size = var.vm_size + + tags = local.common_tags +} diff --git a/packages/@n8n/benchmark/infra/modules/benchmark-vm/output.tf b/packages/@n8n/benchmark/infra/modules/benchmark-vm/output.tf new file mode 100644 index 0000000000000..4660ebf4136ab --- /dev/null +++ b/packages/@n8n/benchmark/infra/modules/benchmark-vm/output.tf @@ -0,0 +1,7 @@ +output "vm_name" { + value = azurerm_linux_virtual_machine.main.name +} + +output "ip" { + value = azurerm_public_ip.main.ip_address +} diff --git a/packages/@n8n/benchmark/infra/modules/benchmark-vm/vars.tf b/packages/@n8n/benchmark/infra/modules/benchmark-vm/vars.tf new file mode 100644 index 0000000000000..d671253006e37 --- /dev/null +++ b/packages/@n8n/benchmark/infra/modules/benchmark-vm/vars.tf @@ -0,0 +1,31 @@ +variable "location" { + description = "Region to deploy resources" + default = "East US" +} + +variable "resource_group_name" { + description = "Name of the resource group" +} + +variable "prefix" { + description = "Prefix to append to resources" +} + +variable "dedicated_host_id" { + description = "Dedicated Host ID" +} + +variable "ssh_public_key" { + description = "SSH Public Key" +} + +variable "vm_size" { + description = "VM Size" + # 4 vCPUs, 16 GiB memory + default = "Standard_DC4s_v2" +} + +variable "tags" { + description = "Tags to apply to all resources created by this module" + type = map(string) +} diff --git a/packages/@n8n/benchmark/infra/modules/benchmark-vm/vm.tf b/packages/@n8n/benchmark/infra/modules/benchmark-vm/vm.tf new file mode 100644 index 0000000000000..651a9d2a0bdc4 --- /dev/null +++ b/packages/@n8n/benchmark/infra/modules/benchmark-vm/vm.tf @@ -0,0 +1,136 @@ +# Network + +resource "azurerm_virtual_network" "main" { + name = "${var.prefix}-vnet" + location = var.location + resource_group_name = var.resource_group_name + address_space = ["10.0.0.0/16"] + + tags = var.tags +} + +resource "azurerm_subnet" "main" { + name = "${var.prefix}-subnet" + resource_group_name = var.resource_group_name + virtual_network_name = azurerm_virtual_network.main.name + address_prefixes = ["10.0.0.0/24"] +} + +resource "azurerm_network_security_group" "ssh" { + name = "${var.prefix}-nsg" + location = var.location + resource_group_name = var.resource_group_name + + security_rule { + name = "AllowSSH" + priority = 1001 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_range = "22" + source_address_prefix = "*" + destination_address_prefix = "*" + } + + tags = var.tags +} + +resource "azurerm_public_ip" "main" { + name = "${var.prefix}-pip" + location = var.location + resource_group_name = var.resource_group_name + allocation_method = "Static" + sku = "Standard" + + tags = var.tags +} + +resource "azurerm_network_interface" "main" { + name = "${var.prefix}-nic" + location = var.location + resource_group_name = var.resource_group_name + + ip_configuration { + name = "${var.prefix}-ipconfig" + subnet_id = azurerm_subnet.main.id + private_ip_address_allocation = "Dynamic" + public_ip_address_id = azurerm_public_ip.main.id + } + + tags = var.tags +} + +resource "azurerm_network_interface_security_group_association" "ssh" { + network_interface_id = azurerm_network_interface.main.id + network_security_group_id = azurerm_network_security_group.ssh.id +} + +# Disk + +resource "azurerm_managed_disk" "data" { + name = "${var.prefix}-disk" + location = var.location + resource_group_name = var.resource_group_name + storage_account_type = "PremiumV2_LRS" + create_option = "Empty" + disk_size_gb = "16" + zone = 1 + + tags = var.tags +} + +resource "azurerm_virtual_machine_data_disk_attachment" "data" { + managed_disk_id = azurerm_managed_disk.data.id + virtual_machine_id = azurerm_linux_virtual_machine.main.id + lun = "1" + caching = "None" +} + +# VM + +resource "azurerm_linux_virtual_machine" "main" { + name = "${var.prefix}-vm" + location = var.location + resource_group_name = var.resource_group_name + network_interface_ids = [azurerm_network_interface.main.id] + dedicated_host_id = var.dedicated_host_id + zone = 1 + + size = var.vm_size + + admin_username = "benchmark" + + admin_ssh_key { + username = "benchmark" + public_key = var.ssh_public_key + } + + os_disk { + caching = "ReadWrite" + storage_account_type = "Premium_LRS" + } + + source_image_reference { + publisher = "Canonical" + offer = "0001-com-ubuntu-server-jammy" + sku = "22_04-lts-gen2" + version = "latest" + } + + identity { + type = "SystemAssigned" + } + + tags = var.tags +} + +resource "azurerm_virtual_machine_extension" "entra_login" { + name = "AADSSHLoginForLinux" + virtual_machine_id = azurerm_linux_virtual_machine.main.id + publisher = "Microsoft.Azure.ActiveDirectory" + type = "AADSSHLoginForLinux" + type_handler_version = "1.0" + + tags = var.tags +} diff --git a/packages/@n8n/benchmark/infra/output.tf b/packages/@n8n/benchmark/infra/output.tf new file mode 100644 index 0000000000000..5caa0adce3e55 --- /dev/null +++ b/packages/@n8n/benchmark/infra/output.tf @@ -0,0 +1,3 @@ +output "vm_name" { + value = module.test_vm.vm_name +} diff --git a/packages/@n8n/benchmark/infra/providers.tf b/packages/@n8n/benchmark/infra/providers.tf new file mode 100644 index 0000000000000..1ce880f5cdadb --- /dev/null +++ b/packages/@n8n/benchmark/infra/providers.tf @@ -0,0 +1,23 @@ + +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 3.115.0" + } + + random = { + source = "hashicorp/random" + } + } + + required_version = "~> 1.8.5" +} + +provider "azurerm" { + features {} + + skip_provider_registration = true +} + +provider "random" {} diff --git a/packages/@n8n/benchmark/infra/vars.tf b/packages/@n8n/benchmark/infra/vars.tf new file mode 100644 index 0000000000000..379bafe80baa0 --- /dev/null +++ b/packages/@n8n/benchmark/infra/vars.tf @@ -0,0 +1,34 @@ +variable "location" { + description = "Region to deploy resources" + default = "East US" +} + +variable "resource_group_name" { + description = "Name of the resource group" + default = "n8n-benchmarking" +} + +variable "host_size_family" { + description = "Size Family for the Host Group" + default = "DCSv2-Type1" +} + +variable "vm_size" { + description = "VM Size" + # 2 vCPUs, 8 GiB memory + default = "Standard_DC2s_v2" +} + +variable "number_of_vms" { + description = "Number of VMs to create" + default = 1 +} + +locals { + common_tags = { + Id = "N8nBenchmark" + Terraform = "true" + Owner = "Catalysts" + CreatedAt = timestamp() + } +} diff --git a/packages/@n8n/benchmark/package.json b/packages/@n8n/benchmark/package.json index 2a3ad1c214151..81588845b759a 100644 --- a/packages/@n8n/benchmark/package.json +++ b/packages/@n8n/benchmark/package.json @@ -10,6 +10,8 @@ "start": "./bin/n8n-benchmark", "test": "echo \"Error: no test specified\" && exit 1", "typecheck": "tsc --noEmit", + "run-in-cloud": "zx scripts/runInCloud.mjs", + "destroy-cloud-env": "zx scripts/destroyCloudEnv.mjs", "watch": "concurrently \"tsc -w -p tsconfig.build.json\" \"tsc-alias -w -p tsconfig.build.json\"" }, "engines": { diff --git a/packages/@n8n/benchmark/scripts/destroyCloudEnv.mjs b/packages/@n8n/benchmark/scripts/destroyCloudEnv.mjs new file mode 100644 index 0000000000000..1ffc852aeaab8 --- /dev/null +++ b/packages/@n8n/benchmark/scripts/destroyCloudEnv.mjs @@ -0,0 +1,90 @@ +#!/usr/bin/env zx +/** + * Script that deletes all resources created by the benchmark environment + * and that are older than 2 hours. + * + * Even tho the environment is provisioned using terraform, the terraform + * state is not persisted. Hence we can't use terraform to delete the resources. + * We could store the state to a storage account, but then we wouldn't be able + * to spin up new envs on-demand. Hence this design. + * + * Usage: + * zx scripts/deleteCloudEnv.mjs + */ +// @ts-check +import { $ } from 'zx'; + +const EXPIRE_TIME_IN_H = 2; +const EXPIRE_TIME_IN_MS = EXPIRE_TIME_IN_H * 60 * 60 * 1000; +const RESOURCE_GROUP_NAME = 'n8n-benchmarking'; + +async function main() { + const resourcesResult = + await $`az resource list --resource-group ${RESOURCE_GROUP_NAME} --query "[?tags.Id == 'N8nBenchmark'].{id:id, createdAt:tags.CreatedAt}" -o json`; + + const resources = JSON.parse(resourcesResult.stdout); + + const now = Date.now(); + + const resourcesToDelete = resources + .filter((resource) => { + if (resource.createdAt === undefined) { + return true; + } + + const createdAt = new Date(resource.createdAt); + const resourceExpiredAt = createdAt.getTime() + EXPIRE_TIME_IN_MS; + + return now > resourceExpiredAt; + }) + .map((resource) => resource.id); + + if (resourcesToDelete.length === 0) { + if (resources.length === 0) { + console.log('No resources found in the resource group.'); + } else { + console.log( + `Found ${resources.length} resources in the resource group, but none are older than ${EXPIRE_TIME_IN_H} hours.`, + ); + } + + return; + } + + await deleteResources(resourcesToDelete); +} + +async function deleteResources(resourceIds) { + // We don't know the order in which resource should be deleted. + // Here's a poor person's approach to try deletion until all complete + const MAX_ITERATIONS = 100; + let i = 0; + const toDelete = [...resourceIds]; + + console.log(`Deleting ${resourceIds.length} resources...`); + while (toDelete.length > 0) { + const resourceId = toDelete.shift(); + const deleted = await deleteById(resourceId); + if (!deleted) { + toDelete.push(resourceId); + } + + if (i++ > MAX_ITERATIONS) { + console.log( + `Max iterations reached. Exiting. Could not delete ${toDelete.length} resources.`, + ); + process.exit(1); + } + } +} + +async function deleteById(id) { + try { + await $`az resource delete --ids ${id}`; + return true; + } catch (error) { + return false; + } +} + +main(); diff --git a/packages/@n8n/benchmark/scripts/runInCloud.mjs b/packages/@n8n/benchmark/scripts/runInCloud.mjs new file mode 100755 index 0000000000000..9d6f2b9556da3 --- /dev/null +++ b/packages/@n8n/benchmark/scripts/runInCloud.mjs @@ -0,0 +1,200 @@ +#!/usr/bin/env zx +/** + * Script to run benchmarks on the cloud benchmark environment. + * This script will: + * 1. Provision a benchmark environment using Terraform. + * 2. Run the benchmarks on the VM. + * 3. Destroy the cloud environment. + * + * NOTE: Must be run in the root of the package. + * + * Usage: + * zx scripts/runBenchmarksOnCloud.mjs [--debug] + * + */ +// @ts-check +import fs from 'fs'; +import minimist from 'minimist'; +import { $, sleep, which } from 'zx'; +import path from 'path'; +import { SshClient } from './sshClient.mjs'; +import { TerraformClient } from './terraformClient.mjs'; + +/** + * @typedef {Object} BenchmarkEnv + * @property {string} vmName + */ + +const RESOURCE_GROUP_NAME = 'n8n-benchmarking'; + +const paths = { + n8nSetupsDir: path.join(path.resolve('scripts'), 'runOnVm', 'n8nSetups'), +}; + +async function main() { + const config = await parseAndValidateConfig(); + await ensureDependencies(); + + console.log('Using n8n tag', config.n8nTag); + console.log('Using benchmark cli tag', config.benchmarkTag); + + const terraformClient = new TerraformClient({ + privateKeyPath: paths.privateKeyPath, + isVerbose: config.isVerbose, + }); + + try { + const benchmarkEnv = await terraformClient.provisionEnvironment(); + + await runBenchmarksOnVm(config, benchmarkEnv); + } catch (error) { + console.error('An error occurred while running the benchmarks:'); + console.error(error); + } finally { + await terraformClient.destroyEnvironment(); + } +} + +async function ensureDependencies() { + await which('terraform'); + await which('az'); +} + +/** + * + * @param {Config} config + * @param {BenchmarkEnv} benchmarkEnv + */ +async function runBenchmarksOnVm(config, benchmarkEnv) { + console.log(`Setting up the environment for ${config.n8nSetupToUse}...`); + + const sshClient = new SshClient({ + vmName: benchmarkEnv.vmName, + resourceGroupName: RESOURCE_GROUP_NAME, + verbose: config.isVerbose, + }); + + await ensureVmIsReachable(sshClient); + + const scriptsDir = await transferScriptsToVm(sshClient); + + // Bootstrap the environment with dependencies + console.log('Running bootstrap script...'); + const bootstrapScriptPath = path.join(scriptsDir, 'bootstrap.sh'); + await sshClient.ssh(`chmod a+x ${bootstrapScriptPath} && ${bootstrapScriptPath}`); + + // Give some time for the VM to be ready + await sleep(1000); + + console.log('Running benchmarks...'); + const runScriptPath = path.join(scriptsDir, 'runOnVm.mjs'); + + const flags = { + n8nDockerTag: config.n8nTag, + benchmarkDockerTag: config.benchmarkTag, + k6ApiToken: config.k6ApiToken, + }; + + const flagsString = Object.entries(flags) + .filter(([, value]) => value !== undefined) + .map(([key, value]) => `--${key}=${value}`) + .join(' '); + + await sshClient.ssh(`npx zx ${runScriptPath} ${flagsString} ${config.n8nSetupToUse}`, { + // Test run should always log its output + verbose: true, + }); +} + +async function ensureVmIsReachable(sshClient) { + await sshClient.ssh('echo "VM is reachable"'); +} + +/** + * @returns Path where the scripts are located on the VM + */ +async function transferScriptsToVm(sshClient) { + await sshClient.ssh('rm -rf ~/n8n'); + + await sshClient.ssh('git clone --depth=1 https://github.com/n8n-io/n8n.git'); + + return '~/n8n/packages/@n8n/benchmark/scripts/runOnVm'; +} + +function readAvailableN8nSetups() { + const setups = fs.readdirSync(paths.n8nSetupsDir); + + return setups; +} + +/** + * @typedef {Object} Config + * @property {boolean} isVerbose + * @property {string} n8nSetupToUse + * @property {string} n8nTag + * @property {string} benchmarkTag + * @property {string} [k6ApiToken] + * + * @returns {Promise} + */ +async function parseAndValidateConfig() { + const args = minimist(process.argv.slice(2), { + boolean: ['debug'], + }); + + const n8nSetupToUse = await getAndValidateN8nSetup(args); + const isVerbose = args.debug || false; + const n8nTag = args.n8nTag || process.env.N8N_DOCKER_TAG || 'latest'; + const benchmarkTag = args.benchmarkTag || process.env.BENCHMARK_DOCKER_TAG || 'latest'; + const k6ApiToken = args.k6ApiToken || process.env.K6_API_TOKEN || undefined; + + return { + isVerbose, + n8nSetupToUse, + n8nTag, + benchmarkTag, + k6ApiToken, + }; +} + +/** + * @param {minimist.ParsedArgs} args + */ +async function getAndValidateN8nSetup(args) { + // Last parameter is the n8n setup to use + const n8nSetupToUse = args._[args._.length - 1]; + + if (!n8nSetupToUse) { + printUsage(); + process.exit(1); + } + + const availableSetups = readAvailableN8nSetups(); + + if (!availableSetups.includes(n8nSetupToUse)) { + printUsage(); + process.exit(1); + } + + return n8nSetupToUse; +} + +function printUsage() { + const availableSetups = readAvailableN8nSetups(); + + console.log('Usage: zx scripts/runInCloud.mjs '); + console.log(' eg: zx scripts/runInCloud.mjs sqlite'); + console.log(''); + console.log('Options:'); + console.log(' --debug Enable verbose output'); + console.log(' --n8nTag Docker tag for n8n image. Default is latest'); + console.log(' --benchmarkTag Docker tag for benchmark cli image. Default is latest'); + console.log( + ' --k6ApiToken API token for k6 cloud. Default is read from K6_API_TOKEN env var. If omitted, k6 cloud will not be used.', + ); + console.log(''); + console.log('Available setups:'); + console.log(` ${availableSetups.join(', ')}`); +} + +main().catch(console.error); diff --git a/packages/@n8n/benchmark/scripts/runOnVm/bootstrap.sh b/packages/@n8n/benchmark/scripts/runOnVm/bootstrap.sh new file mode 100644 index 0000000000000..9a5ffbe25319e --- /dev/null +++ b/packages/@n8n/benchmark/scripts/runOnVm/bootstrap.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# +# Script to initialize the benchmark environment on a VM +# + +set -euo pipefail; + +CURRENT_USER=$(whoami) + +# Mount the data disk +if [ -d "/n8n" ]; then + echo "Data disk already mounted. Clearing it..." + rm -rf /n8n/* + rm -rf /n8n/.[!.]* +else + sudo mkdir -p /n8n + sudo parted /dev/sdc --script mklabel gpt mkpart xfspart xfs 0% 100% + sudo mkfs.xfs /dev/sdc1 + sudo partprobe /dev/sdc1 + sudo mount /dev/sdc1 /n8n +fi + +# Allow the current user to write to the data disk +sudo chmod a+rw /n8n + +# Include nodejs v20 repository +curl -fsSL https://deb.nodesource.com/setup_20.x -o nodesource_setup.sh +sudo -E bash nodesource_setup.sh + +# Install docker, docker compose and nodejs +sudo DEBIAN_FRONTEND=noninteractive apt-get update +sudo DEBIAN_FRONTEND=noninteractive apt-get install -y docker.io docker-compose nodejs + +# Add the current user to the docker group +sudo usermod -aG docker "$CURRENT_USER" + +# Install zx +npm install zx diff --git a/packages/@n8n/benchmark/scripts/runOnVm/n8nSetups/sqlite/docker-compose.yml b/packages/@n8n/benchmark/scripts/runOnVm/n8nSetups/sqlite/docker-compose.yml new file mode 100644 index 0000000000000..02b61961f1b4a --- /dev/null +++ b/packages/@n8n/benchmark/scripts/runOnVm/n8nSetups/sqlite/docker-compose.yml @@ -0,0 +1,17 @@ +services: + n8n: + image: ghcr.io/n8n-io/n8n:${N8N_VERSION:-latest} + environment: + - N8N_DIAGNOSTICS_ENABLED=false + - N8N_USER_FOLDER=/n8n + ports: + - 5678:5678 + volumes: + - /n8n:/n8n + benchmark: + image: ghcr.io/n8n-io/n8n-benchmark:${N8N_BENCHMARK_VERSION:-latest} + depends_on: + - n8n + environment: + - N8N_BASE_URL=http://n8n:5678 + - K6_API_TOKEN=${K6_API_TOKEN} diff --git a/packages/@n8n/benchmark/scripts/runOnVm/runOnVm.mjs b/packages/@n8n/benchmark/scripts/runOnVm/runOnVm.mjs new file mode 100755 index 0000000000000..e3402e70c58bd --- /dev/null +++ b/packages/@n8n/benchmark/scripts/runOnVm/runOnVm.mjs @@ -0,0 +1,55 @@ +#!/usr/bin/env zx +/** + * This script runs the benchmarks using a given docker compose setup + */ + +import { $ } from 'zx'; + +const [n8nSetupToUse] = argv._; + +if (!n8nSetupToUse) { + printUsage(); + process.exit(1); +} + +function printUsage() { + console.log('Usage: zx runOnVm.mjs '); + console.log(' eg: zx runOnVm.mjs sqlite'); +} + +async function main() { + const composeFilePath = path.join(__dirname, 'n8nSetups', n8nSetupToUse); + const n8nTag = argv.n8nDockerTag || process.env.N8N_DOCKER_TAG || 'latest'; + const benchmarkTag = argv.benchmarkDockerTag || process.env.BENCHMARK_DOCKER_TAG || 'latest'; + const k6ApiToken = argv.k6ApiToken || process.env.K6_API_TOKEN || undefined; + + const $$ = $({ + cwd: composeFilePath, + verbose: true, + env: { + N8N_VERSION: n8nTag, + BENCHMARK_VERSION: benchmarkTag, + K6_API_TOKEN: k6ApiToken, + }, + }); + + try { + await $$`docker-compose up -d n8n`; + + await $$`docker-compose run benchmark run`; + } catch (error) { + console.error('An error occurred while running the benchmarks:'); + console.error(error); + console.error(''); + await dumpN8nInstanceLogs($$); + } finally { + await $$`docker-compose down`; + } +} + +async function dumpN8nInstanceLogs($$) { + console.error('n8n instance logs:'); + await $$`docker-compose logs n8n`; +} + +main(); diff --git a/packages/@n8n/benchmark/scripts/sshClient.mjs b/packages/@n8n/benchmark/scripts/sshClient.mjs new file mode 100644 index 0000000000000..033312f48ed77 --- /dev/null +++ b/packages/@n8n/benchmark/scripts/sshClient.mjs @@ -0,0 +1,28 @@ +// @ts-check +import { $ } from 'zx'; + +export class SshClient { + /** + * + * @param {{ vmName: string; resourceGroupName: string; verbose?: boolean }} param0 + */ + constructor({ vmName, resourceGroupName, verbose = false }) { + this.vmName = vmName; + this.resourceGroupName = resourceGroupName; + this.verbose = verbose; + + this.$$ = $({ + verbose, + }); + } + + /** + * @param {string} command + * @param {{ verbose?: boolean }} [options] + */ + async ssh(command, options = {}) { + const $$ = options?.verbose ? $({ verbose: true }) : this.$$; + + await $$`az ssh vm -n ${this.vmName} -g ${this.resourceGroupName} --yes -- -o StrictHostKeyChecking=accept-new ${command}`; + } +} diff --git a/packages/@n8n/benchmark/scripts/terraformClient.mjs b/packages/@n8n/benchmark/scripts/terraformClient.mjs new file mode 100644 index 0000000000000..1ba4fcedb468e --- /dev/null +++ b/packages/@n8n/benchmark/scripts/terraformClient.mjs @@ -0,0 +1,53 @@ +// @ts-check + +import path from 'path'; +import { $, fs } from 'zx'; + +const paths = { + infraCodeDir: path.resolve('infra'), + terraformStateFile: path.join(path.resolve('infra'), 'terraform.tfstate'), +}; + +export class TerraformClient { + constructor({ privateKeyPath, isVerbose = false }) { + this.privateKeyPath = privateKeyPath; + this.isVerbose = isVerbose; + this.$$ = $({ + cwd: paths.infraCodeDir, + verbose: isVerbose, + }); + } + + /** + * @typedef {Object} BenchmarkEnv + * @property {string} vmName + * + * @returns {Promise} + */ + async provisionEnvironment() { + console.log('Provisioning cloud environment...'); + + await this.$$`terraform init`; + await this.$$`terraform apply -input=false -auto-approve`; + + return { + vmName: await this.getTerraformOutput('vm_name'), + }; + } + + async destroyEnvironment() { + if (!fs.existsSync(paths.terraformStateFile)) { + console.log('No cloud environment to destroy. Skipping...'); + return; + } + + console.log('Destroying cloud environment...'); + + await this.$$`terraform destroy -input=false -auto-approve`; + } + + async getTerraformOutput(key) { + const output = await this.$$`terraform output -raw ${key}`; + return output.stdout.trim(); + } +} diff --git a/packages/@n8n/benchmark/src/commands/run.ts b/packages/@n8n/benchmark/src/commands/run.ts index d69b4a54d4afd..3d9a3bf803e45 100644 --- a/packages/@n8n/benchmark/src/commands/run.ts +++ b/packages/@n8n/benchmark/src/commands/run.ts @@ -25,7 +25,11 @@ export default class RunCommand extends Command { const scenarioRunner = new ScenarioRunner( new N8nApiClient(config.get('n8n.baseUrl')), new ScenarioDataFileLoader(), - new K6Executor(config.get('k6ExecutablePath'), config.get('n8n.baseUrl')), + new K6Executor({ + k6ExecutablePath: config.get('k6.executablePath'), + k6ApiToken: config.get('k6.apiToken'), + n8nApiBaseUrl: config.get('n8n.baseUrl'), + }), { email: config.get('n8n.user.email'), password: config.get('n8n.user.password'), diff --git a/packages/@n8n/benchmark/src/config/config.ts b/packages/@n8n/benchmark/src/config/config.ts index 896ecc9296da0..2fa8d9249bf8f 100644 --- a/packages/@n8n/benchmark/src/config/config.ts +++ b/packages/@n8n/benchmark/src/config/config.ts @@ -31,11 +31,19 @@ const configSchema = { }, }, }, - k6ExecutablePath: { - doc: 'The path to the k6 binary', - format: String, - default: 'k6', - env: 'K6_PATH', + k6: { + executablePath: { + doc: 'The path to the k6 binary', + format: String, + default: 'k6', + env: 'K6_PATH', + }, + apiToken: { + doc: 'The API token for k6 cloud', + format: String, + default: undefined, + env: 'K6_API_TOKEN', + }, }, }; diff --git a/packages/@n8n/benchmark/src/n8nApiClient/n8nApiClient.ts b/packages/@n8n/benchmark/src/n8nApiClient/n8nApiClient.ts index b1ec2d5162bb1..a188c4eefe913 100644 --- a/packages/@n8n/benchmark/src/n8nApiClient/n8nApiClient.ts +++ b/packages/@n8n/benchmark/src/n8nApiClient/n8nApiClient.ts @@ -49,6 +49,12 @@ export class N8nApiClient { } else if (response.status === 400) { if (responsePayload.message === 'Instance owner already setup') console.log('Owner already set up'); + } else if (response.status === 404) { + // The n8n instance setup owner endpoint not be available yet even tho + // the health endpoint returns ok. In this case we simply retry. + console.log('Owner setup endpoint not available yet, retrying in 1s...'); + await this.delay(1000); + await this.setupOwnerIfNeeded(loginDetails); } else { throw new Error( `Owner setup failed with status ${response.status}: ${responsePayload.message}`, diff --git a/packages/@n8n/benchmark/src/testExecution/k6Executor.ts b/packages/@n8n/benchmark/src/testExecution/k6Executor.ts index 176f1c15bb693..a9ab325b5f572 100644 --- a/packages/@n8n/benchmark/src/testExecution/k6Executor.ts +++ b/packages/@n8n/benchmark/src/testExecution/k6Executor.ts @@ -1,38 +1,103 @@ -import { $, which } from 'zx'; +import fs from 'fs'; +import path from 'path'; +import { $, which, tmpfile } from 'zx'; import type { Scenario } from '@/types/scenario'; +export type K6ExecutorOpts = { + k6ExecutablePath: string; + k6ApiToken?: string; + n8nApiBaseUrl: string; +}; + +/** + * Flag for the k6 CLI. + * @example ['--duration', '1m'] + * @example ['--quiet'] + */ +type K6CliFlag = [string] | [string, string]; + /** * Executes test scenarios using k6 */ export class K6Executor { - constructor( - private readonly k6ExecutablePath: string, - private readonly n8nApiBaseUrl: string, - ) {} + /** + * This script is dynamically injected into the k6 test script to generate + * a summary report of the test execution. + */ + private readonly handleSummaryScript = ` +import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js'; +export function handleSummary(data) { + return { + stdout: textSummary(data), + '{{scenarioName}}.summary.json': JSON.stringify(data), + }; +} +`; + + constructor(private readonly opts: K6ExecutorOpts) {} async executeTestScenario(scenario: Scenario) { - // For 1 min with 5 virtual users - const stage = '1m:5'; + const augmentedTestScriptPath = this.augmentSummaryScript(scenario); + const runDirPath = path.dirname(augmentedTestScriptPath); + + const flags: K6CliFlag[] = [['--quiet'], ['--duration', '1m'], ['--vus', '5']]; + + if (this.opts.k6ApiToken) { + flags.push(['--out', 'cloud']); + } + + const flattedFlags = flags.flat(2); const k6ExecutablePath = await this.resolveK6ExecutablePath(); const processPromise = $({ - cwd: scenario.scenarioDirPath, + cwd: runDirPath, env: { - API_BASE_URL: this.n8nApiBaseUrl, + API_BASE_URL: this.opts.n8nApiBaseUrl, + K6_CLOUD_TOKEN: this.opts.k6ApiToken, }, - })`${k6ExecutablePath} run --quiet --stage ${stage} ${scenario.scriptPath}`; + })`${k6ExecutablePath} run ${flattedFlags} ${augmentedTestScriptPath}`; for await (const chunk of processPromise.stdout) { console.log((chunk as Buffer).toString()); } + + this.loadEndOfTestSummary(runDirPath, scenario.name); + } + + /** + * Augments the test script with a summary script + * + * @returns Absolute path to the augmented test script + */ + private augmentSummaryScript(scenario: Scenario) { + const fullTestScriptPath = path.join(scenario.scenarioDirPath, scenario.scriptPath); + const testScript = fs.readFileSync(fullTestScriptPath, 'utf8'); + const summaryScript = this.handleSummaryScript.replace('{{scenarioName}}', scenario.name); + + const augmentedTestScript = `${testScript}\n\n${summaryScript}`; + + const tempFilePath = tmpfile(`${scenario.name}.ts`, augmentedTestScript); + + return tempFilePath; + } + + private loadEndOfTestSummary(dir: string, scenarioName: string): K6EndOfTestSummary { + const summaryReportPath = path.join(dir, `${scenarioName}.summary.json`); + const summaryReport = fs.readFileSync(summaryReportPath, 'utf8'); + + try { + return JSON.parse(summaryReport); + } catch (error) { + throw new Error(`Failed to parse the summary report at ${summaryReportPath}`); + } } /** * @returns Resolved path to the k6 executable */ private async resolveK6ExecutablePath(): Promise { - const k6ExecutablePath = await which(this.k6ExecutablePath, { nothrow: true }); + const k6ExecutablePath = await which(this.opts.k6ExecutablePath, { nothrow: true }); if (!k6ExecutablePath) { throw new Error( 'Could not find k6 executable based on your `PATH`. Please ensure k6 is available in your system and add it to your `PATH` or specify the path to the k6 executable using the `K6_PATH` environment variable.', diff --git a/packages/@n8n/benchmark/src/testExecution/k6Summary.ts b/packages/@n8n/benchmark/src/testExecution/k6Summary.ts new file mode 100644 index 0000000000000..c40b0cae97526 --- /dev/null +++ b/packages/@n8n/benchmark/src/testExecution/k6Summary.ts @@ -0,0 +1,255 @@ +/** +Example JSON: + +{ + "options": { + "summaryTrendStats": ["avg", "min", "med", "max", "p(90)", "p(95)"], + "summaryTimeUnit": "", + "noColor": false + }, + "state": { "isStdOutTTY": false, "isStdErrTTY": false, "testRunDurationMs": 23.374 }, + "metrics": { + "http_req_tls_handshaking": { + "type": "trend", + "contains": "time", + "values": { "avg": 0, "min": 0, "med": 0, "max": 0, "p(90)": 0, "p(95)": 0 } + }, + "checks": { + "type": "rate", + "contains": "default", + "values": { "rate": 1, "passes": 1, "fails": 0 } + }, + "http_req_sending": { + "type": "trend", + "contains": "time", + "values": { + "p(90)": 0.512, + "p(95)": 0.512, + "avg": 0.512, + "min": 0.512, + "med": 0.512, + "max": 0.512 + } + }, + "http_reqs": { + "contains": "default", + "values": { "count": 1, "rate": 42.78257893385813 }, + "type": "counter" + }, + "http_req_blocked": { + "contains": "time", + "values": { + "avg": 1.496, + "min": 1.496, + "med": 1.496, + "max": 1.496, + "p(90)": 1.496, + "p(95)": 1.496 + }, + "type": "trend" + }, + "data_received": { + "type": "counter", + "contains": "data", + "values": { "count": 269, "rate": 11508.513733207838 } + }, + "iterations": { + "type": "counter", + "contains": "default", + "values": { "count": 1, "rate": 42.78257893385813 } + }, + "http_req_waiting": { + "type": "trend", + "contains": "time", + "values": { + "p(95)": 18.443, + "avg": 18.443, + "min": 18.443, + "med": 18.443, + "max": 18.443, + "p(90)": 18.443 + } + }, + "http_req_receiving": { + "type": "trend", + "contains": "time", + "values": { + "avg": 0.186, + "min": 0.186, + "med": 0.186, + "max": 0.186, + "p(90)": 0.186, + "p(95)": 0.186 + } + }, + "http_req_duration{expected_response:true}": { + "type": "trend", + "contains": "time", + "values": { + "max": 19.141, + "p(90)": 19.141, + "p(95)": 19.141, + "avg": 19.141, + "min": 19.141, + "med": 19.141 + } + }, + "iteration_duration": { + "type": "trend", + "contains": "time", + "values": { + "avg": 22.577833, + "min": 22.577833, + "med": 22.577833, + "max": 22.577833, + "p(90)": 22.577833, + "p(95)": 22.577833 + } + }, + "http_req_connecting": { + "type": "trend", + "contains": "time", + "values": { + "avg": 0.673, + "min": 0.673, + "med": 0.673, + "max": 0.673, + "p(90)": 0.673, + "p(95)": 0.673 + } + }, + "http_req_failed": { + "type": "rate", + "contains": "default", + "values": { "rate": 0, "passes": 0, "fails": 1 } + }, + "http_req_duration": { + "type": "trend", + "contains": "time", + "values": { + "p(90)": 19.141, + "p(95)": 19.141, + "avg": 19.141, + "min": 19.141, + "med": 19.141, + "max": 19.141 + } + }, + "data_sent": { + "type": "counter", + "contains": "data", + "values": { "count": 102, "rate": 4363.82305125353 } + } + }, + "root_group": { + "name": "", + "path": "", + "id": "d41d8cd98f00b204e9800998ecf8427e", + "groups": [], + "checks": [ + { + "name": "is status 200", + "path": "::is status 200", + "id": "548d37ca5f33793206f7832e7cea54fb", + "passes": 1, + "fails": 0 + } + ] + } +} + */ + +type TrendStat = 'avg' | 'min' | 'med' | 'max' | 'p(90)' | 'p(95)'; +type MetricType = 'trend' | 'rate' | 'counter'; +type MetricContains = 'time' | 'default' | 'data'; + +interface TrendValues { + avg: number; + min: number; + med: number; + max: number; + 'p(90)': number; + 'p(95)': number; +} + +interface RateValues { + rate: number; + passes: number; + fails: number; +} + +interface CounterValues { + count: number; + rate: number; +} + +interface TrendMetric { + type: 'trend'; + contains: 'time'; + values: TrendValues; +} + +interface RateMetric { + type: 'rate'; + contains: 'default'; + values: RateValues; +} + +interface CounterMetric { + type: 'counter'; + contains: MetricContains; + values: CounterValues; +} + +interface Options { + summaryTrendStats: TrendStat[]; + summaryTimeUnit: string; + noColor: boolean; +} + +interface State { + isStdOutTTY: boolean; + isStdErrTTY: boolean; + testRunDurationMs: number; +} + +interface Metrics { + http_req_tls_handshaking: TrendMetric; + checks: RateMetric; + http_req_sending: TrendMetric; + http_reqs: CounterMetric; + http_req_blocked: TrendMetric; + data_received: CounterMetric; + iterations: CounterMetric; + http_req_waiting: TrendMetric; + http_req_receiving: TrendMetric; + 'http_req_duration{expected_response:true}': TrendMetric; + iteration_duration: TrendMetric; + http_req_connecting: TrendMetric; + http_req_failed: RateMetric; + http_req_duration: TrendMetric; + data_sent: CounterMetric; +} + +interface Check { + name: string; + path: string; + id: string; + passes: number; + fails: number; +} + +interface RootGroup { + name: string; + path: string; + id: string; + groups: any[]; + checks: Check[]; +} + +interface K6EndOfTestSummary { + options: Options; + state: State; + metrics: Metrics; + root_group: RootGroup; +} diff --git a/packages/@n8n/nodes-langchain/package.json b/packages/@n8n/nodes-langchain/package.json index 28303136c215c..7e114c415611a 100644 --- a/packages/@n8n/nodes-langchain/package.json +++ b/packages/@n8n/nodes-langchain/package.json @@ -181,7 +181,7 @@ "sqlite3": "5.1.7", "temp": "0.9.4", "tmp-promise": "3.0.3", - "zod": "3.23.8", + "zod": "catalog:", "zod-to-json-schema": "3.23.0" } } diff --git a/packages/@n8n_io/eslint-config/frontend.js b/packages/@n8n_io/eslint-config/frontend.js index c801a6007cf9f..cd62a6059a352 100644 --- a/packages/@n8n_io/eslint-config/frontend.js +++ b/packages/@n8n_io/eslint-config/frontend.js @@ -66,6 +66,12 @@ module.exports = { useAttrs: 'attrs', }, ], + 'vue/block-order': [ + 'error', + { + order: ['script', 'template', 'style'], + }, + ], // TODO: fix these '@typescript-eslint/no-unsafe-call': 'off', diff --git a/packages/cli/package.json b/packages/cli/package.json index c7608d1c34689..481c91032a512 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -173,6 +173,6 @@ "xmllint-wasm": "3.0.1", "xss": "^1.0.14", "yamljs": "0.3.0", - "zod": "3.22.4" + "zod": "catalog:" } } diff --git a/packages/cli/src/Interfaces.ts b/packages/cli/src/Interfaces.ts index f6c3f4670fb74..18102f8f4ca72 100644 --- a/packages/cli/src/Interfaces.ts +++ b/packages/cli/src/Interfaces.ts @@ -42,7 +42,7 @@ import type { WorkflowRepository } from '@db/repositories/workflow.repository'; import type { ExternalHooks } from './external-hooks'; import type { LICENSE_FEATURES, LICENSE_QUOTAS } from './constants'; import type { WorkflowWithSharingsAndCredentials } from './workflows/workflows.types'; -import type { RunningJobSummary } from './scaling/types'; +import type { RunningJobSummary } from './scaling/scaling.types'; import type { Scope } from '@n8n/permissions'; export interface ICredentialsTypeData { diff --git a/packages/cli/src/PublicApi/v1/handlers/sourceControl/sourceControl.handler.ts b/packages/cli/src/PublicApi/v1/handlers/sourceControl/sourceControl.handler.ts index 7a3cf08ec0eb5..330701f59cad0 100644 --- a/packages/cli/src/PublicApi/v1/handlers/sourceControl/sourceControl.handler.ts +++ b/packages/cli/src/PublicApi/v1/handlers/sourceControl/sourceControl.handler.ts @@ -3,13 +3,13 @@ import { Container } from 'typedi'; import type { StatusResult } from 'simple-git'; import type { PublicSourceControlRequest } from '../../../types'; import { globalScope } from '../../shared/middlewares/global.middleware'; -import type { ImportResult } from '@/environments/sourceControl/types/importResult'; -import { SourceControlService } from '@/environments/sourceControl/sourceControl.service.ee'; -import { SourceControlPreferencesService } from '@/environments/sourceControl/sourceControlPreferences.service.ee'; +import type { ImportResult } from '@/environments/source-control/types/import-result'; +import { SourceControlService } from '@/environments/source-control/source-control.service.ee'; +import { SourceControlPreferencesService } from '@/environments/source-control/source-control-preferences.service.ee'; import { getTrackingInformationFromPullResult, isSourceControlLicensed, -} from '@/environments/sourceControl/sourceControlHelper.ee'; +} from '@/environments/source-control/source-control-helper.ee'; import { EventService } from '@/events/event.service'; export = { diff --git a/packages/cli/src/__tests__/workflow-execute-additional-data.test.ts b/packages/cli/src/__tests__/workflow-execute-additional-data.test.ts index f51d994493cc4..5bd84bf88ff84 100644 --- a/packages/cli/src/__tests__/workflow-execute-additional-data.test.ts +++ b/packages/cli/src/__tests__/workflow-execute-additional-data.test.ts @@ -1,6 +1,6 @@ import { VariablesService } from '@/environments/variables/variables.service.ee'; import { mockInstance } from '@test/mocking'; -import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus'; +import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus'; import { getBase } from '@/workflow-execute-additional-data'; import Container from 'typedi'; import { CredentialsHelper } from '@/credentials-helper'; diff --git a/packages/cli/src/commands/start.ts b/packages/cli/src/commands/start.ts index 96e3849860c1f..cc8ea6fa93df2 100644 --- a/packages/cli/src/commands/start.ts +++ b/packages/cli/src/commands/start.ts @@ -15,7 +15,7 @@ import { ActiveExecutions } from '@/active-executions'; import { ActiveWorkflowManager } from '@/active-workflow-manager'; import { Server } from '@/server'; import { EDITOR_UI_DIST_DIR, LICENSE_FEATURES } from '@/constants'; -import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus'; +import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus'; import { License } from '@/license'; import { OrchestrationService } from '@/services/orchestration.service'; import { OrchestrationHandlerMainService } from '@/services/orchestration/main/orchestration.handler.main.service'; diff --git a/packages/cli/src/commands/worker.ts b/packages/cli/src/commands/worker.ts index 594b2f56683a5..05062ac78a1c3 100644 --- a/packages/cli/src/commands/worker.ts +++ b/packages/cli/src/commands/worker.ts @@ -12,9 +12,9 @@ import { N8N_VERSION, inTest } from '@/constants'; import type { ICredentialsOverwrite } from '@/Interfaces'; import { CredentialsOverwrites } from '@/credentials-overwrites'; import { rawBodyReader, bodyParser } from '@/middlewares'; -import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus'; +import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus'; import type { RedisServicePubSubSubscriber } from '@/services/redis/RedisServicePubSubSubscriber'; -import { EventMessageGeneric } from '@/eventbus/EventMessageClasses/EventMessageGeneric'; +import { EventMessageGeneric } from '@/eventbus/event-message-classes/event-message-generic'; import { OrchestrationHandlerWorkerService } from '@/services/orchestration/worker/orchestration.handler.worker.service'; import { OrchestrationWorkerService } from '@/services/orchestration/worker/orchestration.worker.service'; import { ServiceUnavailableError } from '@/errors/response-errors/service-unavailable.error'; diff --git a/packages/cli/src/controllers/e2e.controller.ts b/packages/cli/src/controllers/e2e.controller.ts index 6d8eb88fedeca..966221021b51b 100644 --- a/packages/cli/src/controllers/e2e.controller.ts +++ b/packages/cli/src/controllers/e2e.controller.ts @@ -4,7 +4,7 @@ import config from '@/config'; import { SettingsRepository } from '@db/repositories/settings.repository'; import { UserRepository } from '@db/repositories/user.repository'; import { ActiveWorkflowManager } from '@/active-workflow-manager'; -import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus'; +import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus'; import { License } from '@/license'; import { LICENSE_FEATURES, LICENSE_QUOTAS, UNLIMITED_LICENSE_QUOTA, inE2ETests } from '@/constants'; import { Patch, Post, RestController } from '@/decorators'; diff --git a/packages/cli/src/environments/sourceControl/__tests__/source-control-export.service.test.ts b/packages/cli/src/environments/source-control/__tests__/source-control-export.service.test.ts similarity index 93% rename from packages/cli/src/environments/sourceControl/__tests__/source-control-export.service.test.ts rename to packages/cli/src/environments/source-control/__tests__/source-control-export.service.test.ts index 7746875f6386e..77f56d4d964e4 100644 --- a/packages/cli/src/environments/sourceControl/__tests__/source-control-export.service.test.ts +++ b/packages/cli/src/environments/source-control/__tests__/source-control-export.service.test.ts @@ -1,6 +1,6 @@ import mock from 'jest-mock-extended/lib/Mock'; -import { SourceControlExportService } from '../sourceControlExport.service.ee'; -import type { SourceControlledFile } from '../types/sourceControlledFile'; +import { SourceControlExportService } from '../source-control-export.service.ee'; +import type { SourceControlledFile } from '../types/source-controlled-file'; import { Cipher, type InstanceSettings } from 'n8n-core'; import { SharedCredentialsRepository } from '@/databases/repositories/sharedCredentials.repository'; import { mockInstance } from '@test/mocking'; diff --git a/packages/cli/src/environments/sourceControl/__tests__/source-control-git.service.test.ts b/packages/cli/src/environments/source-control/__tests__/source-control-git.service.test.ts similarity index 91% rename from packages/cli/src/environments/sourceControl/__tests__/source-control-git.service.test.ts rename to packages/cli/src/environments/source-control/__tests__/source-control-git.service.test.ts index 145ebe40c048d..dc5ddfbedb97c 100644 --- a/packages/cli/src/environments/sourceControl/__tests__/source-control-git.service.test.ts +++ b/packages/cli/src/environments/source-control/__tests__/source-control-git.service.test.ts @@ -1,5 +1,5 @@ import { mock } from 'jest-mock-extended'; -import { SourceControlGitService } from '../sourceControlGit.service.ee'; +import { SourceControlGitService } from '../source-control-git.service.ee'; import { simpleGit } from 'simple-git'; const MOCK_BRANCHES = { diff --git a/packages/cli/src/environments/sourceControl/__tests__/sourceControlHelper.ee.test.ts b/packages/cli/src/environments/source-control/__tests__/source-control-helper.ee.test.ts similarity index 96% rename from packages/cli/src/environments/sourceControl/__tests__/sourceControlHelper.ee.test.ts rename to packages/cli/src/environments/source-control/__tests__/source-control-helper.ee.test.ts index 8b81bec7b2fde..3009101b5c3b0 100644 --- a/packages/cli/src/environments/sourceControl/__tests__/sourceControlHelper.ee.test.ts +++ b/packages/cli/src/environments/source-control/__tests__/source-control-helper.ee.test.ts @@ -6,18 +6,18 @@ import { getTrackingInformationFromPrePushResult, getTrackingInformationFromPullResult, sourceControlFoldersExistCheck, -} from '@/environments/sourceControl/sourceControlHelper.ee'; +} from '@/environments/source-control/source-control-helper.ee'; import { License } from '@/license'; -import { SourceControlPreferencesService } from '@/environments/sourceControl/sourceControlPreferences.service.ee'; +import { SourceControlPreferencesService } from '@/environments/source-control/source-control-preferences.service.ee'; import { InstanceSettings } from 'n8n-core'; import path from 'path'; import { SOURCE_CONTROL_SSH_FOLDER, SOURCE_CONTROL_GIT_FOLDER, -} from '@/environments/sourceControl/constants'; +} from '@/environments/source-control/constants'; import { constants as fsConstants, accessSync } from 'fs'; -import type { SourceControlledFile } from '@/environments/sourceControl/types/sourceControlledFile'; -import type { SourceControlPreferences } from '@/environments/sourceControl/types/sourceControlPreferences'; +import type { SourceControlledFile } from '@/environments/source-control/types/source-controlled-file'; +import type { SourceControlPreferences } from '@/environments/source-control/types/source-control-preferences'; import { mockInstance } from '@test/mocking'; const pushResult: SourceControlledFile[] = [ diff --git a/packages/cli/src/environments/sourceControl/__tests__/sourceControlGit.service.test.ts b/packages/cli/src/environments/source-control/__tests__/sourceControlGit.service.test.ts similarity index 88% rename from packages/cli/src/environments/sourceControl/__tests__/sourceControlGit.service.test.ts rename to packages/cli/src/environments/source-control/__tests__/sourceControlGit.service.test.ts index f4a458ac3583b..519aef188b4cb 100644 --- a/packages/cli/src/environments/sourceControl/__tests__/sourceControlGit.service.test.ts +++ b/packages/cli/src/environments/source-control/__tests__/sourceControlGit.service.test.ts @@ -1,6 +1,6 @@ -import { SourceControlGitService } from '@/environments/sourceControl/sourceControlGit.service.ee'; +import { SourceControlGitService } from '@/environments/source-control/source-control-git.service.ee'; import { mock } from 'jest-mock-extended'; -import type { SourceControlPreferences } from '@/environments/sourceControl/types/sourceControlPreferences'; +import type { SourceControlPreferences } from '@/environments/source-control/types/source-control-preferences'; import type { User } from '@/databases/entities/User'; import type { SimpleGit } from 'simple-git'; diff --git a/packages/cli/src/environments/sourceControl/constants.ts b/packages/cli/src/environments/source-control/constants.ts similarity index 100% rename from packages/cli/src/environments/sourceControl/constants.ts rename to packages/cli/src/environments/source-control/constants.ts diff --git a/packages/cli/src/environments/sourceControl/middleware/sourceControlEnabledMiddleware.ee.ts b/packages/cli/src/environments/source-control/middleware/source-control-enabled-middleware.ee.ts similarity index 83% rename from packages/cli/src/environments/sourceControl/middleware/sourceControlEnabledMiddleware.ee.ts rename to packages/cli/src/environments/source-control/middleware/source-control-enabled-middleware.ee.ts index cc0debe3a59fc..24317ecbe16bd 100644 --- a/packages/cli/src/environments/sourceControl/middleware/sourceControlEnabledMiddleware.ee.ts +++ b/packages/cli/src/environments/source-control/middleware/source-control-enabled-middleware.ee.ts @@ -1,7 +1,7 @@ import type { RequestHandler } from 'express'; import { Container } from 'typedi'; -import { isSourceControlLicensed } from '../sourceControlHelper.ee'; -import { SourceControlPreferencesService } from '../sourceControlPreferences.service.ee'; +import { isSourceControlLicensed } from '../source-control-helper.ee'; +import { SourceControlPreferencesService } from '../source-control-preferences.service.ee'; export const sourceControlLicensedAndEnabledMiddleware: RequestHandler = (_req, res, next) => { const sourceControlPreferencesService = Container.get(SourceControlPreferencesService); diff --git a/packages/cli/src/environments/sourceControl/sourceControlExport.service.ee.ts b/packages/cli/src/environments/source-control/source-control-export.service.ee.ts similarity index 96% rename from packages/cli/src/environments/sourceControl/sourceControlExport.service.ee.ts rename to packages/cli/src/environments/source-control/source-control-export.service.ee.ts index 6d2d468e30b46..728b44c5dcce7 100644 --- a/packages/cli/src/environments/sourceControl/sourceControlExport.service.ee.ts +++ b/packages/cli/src/environments/source-control/source-control-export.service.ee.ts @@ -10,18 +10,18 @@ import { ApplicationError, type ICredentialDataDecryptedObject } from 'n8n-workf import { writeFile as fsWriteFile, rm as fsRm } from 'fs/promises'; import { rmSync } from 'fs'; import { Credentials, InstanceSettings } from 'n8n-core'; -import type { ExportableWorkflow } from './types/exportableWorkflow'; -import type { ExportableCredential } from './types/exportableCredential'; -import type { ExportResult } from './types/exportResult'; +import type { ExportableWorkflow } from './types/exportable-workflow'; +import type { ExportableCredential } from './types/exportable-credential'; +import type { ExportResult } from './types/export-result'; import { getCredentialExportPath, getVariablesPath, getWorkflowExportPath, sourceControlFoldersExistCheck, stringContainsExpression, -} from './sourceControlHelper.ee'; +} from './source-control-helper.ee'; import type { WorkflowEntity } from '@db/entities/WorkflowEntity'; -import type { SourceControlledFile } from './types/sourceControlledFile'; +import type { SourceControlledFile } from './types/source-controlled-file'; import { VariablesService } from '../variables/variables.service.ee'; import { TagRepository } from '@db/repositories/tag.repository'; import { WorkflowRepository } from '@db/repositories/workflow.repository'; @@ -29,7 +29,7 @@ import { Logger } from '@/logger'; import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository'; import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository'; import { WorkflowTagMappingRepository } from '@db/repositories/workflowTagMapping.repository'; -import type { ResourceOwner } from './types/resourceOwner'; +import type { ResourceOwner } from './types/resource-owner'; @Service() export class SourceControlExportService { diff --git a/packages/cli/src/environments/sourceControl/sourceControlGit.service.ee.ts b/packages/cli/src/environments/source-control/source-control-git.service.ee.ts similarity index 97% rename from packages/cli/src/environments/sourceControl/sourceControlGit.service.ee.ts rename to packages/cli/src/environments/source-control/source-control-git.service.ee.ts index 91220340b7a15..134aab9fd69c7 100644 --- a/packages/cli/src/environments/sourceControl/sourceControlGit.service.ee.ts +++ b/packages/cli/src/environments/source-control/source-control-git.service.ee.ts @@ -11,19 +11,19 @@ import type { SimpleGitOptions, StatusResult, } from 'simple-git'; -import type { SourceControlPreferences } from './types/sourceControlPreferences'; +import type { SourceControlPreferences } from './types/source-control-preferences'; import { SOURCE_CONTROL_DEFAULT_BRANCH, SOURCE_CONTROL_DEFAULT_EMAIL, SOURCE_CONTROL_DEFAULT_NAME, SOURCE_CONTROL_ORIGIN, } from './constants'; -import { sourceControlFoldersExistCheck } from './sourceControlHelper.ee'; +import { sourceControlFoldersExistCheck } from './source-control-helper.ee'; import type { User } from '@db/entities/User'; import { Logger } from '@/logger'; import { ApplicationError } from 'n8n-workflow'; import { OwnershipService } from '@/services/ownership.service'; -import { SourceControlPreferencesService } from './sourceControlPreferences.service.ee'; +import { SourceControlPreferencesService } from './source-control-preferences.service.ee'; @Service() export class SourceControlGitService { diff --git a/packages/cli/src/environments/sourceControl/sourceControlHelper.ee.ts b/packages/cli/src/environments/source-control/source-control-helper.ee.ts similarity index 96% rename from packages/cli/src/environments/sourceControl/sourceControlHelper.ee.ts rename to packages/cli/src/environments/source-control/source-control-helper.ee.ts index 65cd88bfa57ba..738f4212f50e6 100644 --- a/packages/cli/src/environments/sourceControl/sourceControlHelper.ee.ts +++ b/packages/cli/src/environments/source-control/source-control-helper.ee.ts @@ -1,16 +1,16 @@ import { Container } from 'typedi'; import { License } from '@/license'; import { generateKeyPairSync } from 'crypto'; -import type { KeyPair } from './types/keyPair'; +import type { KeyPair } from './types/key-pair'; import { constants as fsConstants, mkdirSync, accessSync } from 'fs'; import { SOURCE_CONTROL_GIT_KEY_COMMENT, SOURCE_CONTROL_TAGS_EXPORT_FILE, SOURCE_CONTROL_VARIABLES_EXPORT_FILE, } from './constants'; -import type { SourceControlledFile } from './types/sourceControlledFile'; +import type { SourceControlledFile } from './types/source-controlled-file'; import path from 'path'; -import type { KeyPairType } from './types/keyPairType'; +import type { KeyPairType } from './types/key-pair-type'; import { Logger } from '@/logger'; export function stringContainsExpression(testString: string): boolean { diff --git a/packages/cli/src/environments/sourceControl/sourceControlImport.service.ee.ts b/packages/cli/src/environments/source-control/source-control-import.service.ee.ts similarity index 98% rename from packages/cli/src/environments/sourceControl/sourceControlImport.service.ee.ts rename to packages/cli/src/environments/source-control/source-control-import.service.ee.ts index bd04937bc1737..05ce8a0687af0 100644 --- a/packages/cli/src/environments/sourceControl/sourceControlImport.service.ee.ts +++ b/packages/cli/src/environments/source-control/source-control-import.service.ee.ts @@ -12,7 +12,7 @@ import { ApplicationError, jsonParse, ErrorReporterProxy as ErrorReporter } from import { readFile as fsReadFile } from 'fs/promises'; import { Credentials, InstanceSettings } from 'n8n-core'; import type { IWorkflowToImport } from '@/Interfaces'; -import type { ExportableCredential } from './types/exportableCredential'; +import type { ExportableCredential } from './types/exportable-credential'; import type { Variables } from '@db/entities/Variables'; import { SharedCredentials } from '@db/entities/SharedCredentials'; import type { WorkflowTagMapping } from '@db/entities/WorkflowTagMapping'; @@ -21,9 +21,9 @@ import { ActiveWorkflowManager } from '@/active-workflow-manager'; // eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import import { In } from '@n8n/typeorm'; import { isUniqueConstraintError } from '@/response-helper'; -import type { SourceControlWorkflowVersionId } from './types/sourceControlWorkflowVersionId'; -import { getCredentialExportPath, getWorkflowExportPath } from './sourceControlHelper.ee'; -import type { SourceControlledFile } from './types/sourceControlledFile'; +import type { SourceControlWorkflowVersionId } from './types/source-control-workflow-version-id'; +import { getCredentialExportPath, getWorkflowExportPath } from './source-control-helper.ee'; +import type { SourceControlledFile } from './types/source-controlled-file'; import { VariablesService } from '../variables/variables.service.ee'; import { TagRepository } from '@db/repositories/tag.repository'; import { WorkflowRepository } from '@db/repositories/workflow.repository'; @@ -35,7 +35,7 @@ import { WorkflowTagMappingRepository } from '@db/repositories/workflowTagMappin import { VariablesRepository } from '@db/repositories/variables.repository'; import { ProjectRepository } from '@/databases/repositories/project.repository'; import type { Project } from '@/databases/entities/Project'; -import type { ResourceOwner } from './types/resourceOwner'; +import type { ResourceOwner } from './types/resource-owner'; import { assertNever } from '@/utils'; import { UserRepository } from '@/databases/repositories/user.repository'; diff --git a/packages/cli/src/environments/sourceControl/sourceControlPreferences.service.ee.ts b/packages/cli/src/environments/source-control/source-control-preferences.service.ee.ts similarity index 97% rename from packages/cli/src/environments/sourceControl/sourceControlPreferences.service.ee.ts rename to packages/cli/src/environments/source-control/source-control-preferences.service.ee.ts index 52a2f6192d84e..4e417704416f0 100644 --- a/packages/cli/src/environments/sourceControl/sourceControlPreferences.service.ee.ts +++ b/packages/cli/src/environments/source-control/source-control-preferences.service.ee.ts @@ -1,10 +1,10 @@ import { writeFile, chmod, readFile } from 'node:fs/promises'; import Container, { Service } from 'typedi'; -import { SourceControlPreferences } from './types/sourceControlPreferences'; +import { SourceControlPreferences } from './types/source-control-preferences'; import type { ValidationError } from 'class-validator'; import { validate } from 'class-validator'; import { rm as fsRm } from 'fs/promises'; -import { generateSshKeyPair, isSourceControlLicensed } from './sourceControlHelper.ee'; +import { generateSshKeyPair, isSourceControlLicensed } from './source-control-helper.ee'; import { Cipher, InstanceSettings } from 'n8n-core'; import { ApplicationError, jsonParse } from 'n8n-workflow'; import { @@ -14,7 +14,7 @@ import { SOURCE_CONTROL_PREFERENCES_DB_KEY, } from './constants'; import path from 'path'; -import type { KeyPairType } from './types/keyPairType'; +import type { KeyPairType } from './types/key-pair-type'; import config from '@/config'; import { Logger } from '@/logger'; import { SettingsRepository } from '@db/repositories/settings.repository'; diff --git a/packages/cli/src/environments/sourceControl/sourceControl.controller.ee.ts b/packages/cli/src/environments/source-control/source-control.controller.ee.ts similarity index 93% rename from packages/cli/src/environments/sourceControl/sourceControl.controller.ee.ts rename to packages/cli/src/environments/source-control/source-control.controller.ee.ts index f92b0bfb1f02f..d927ee2a9bd00 100644 --- a/packages/cli/src/environments/sourceControl/sourceControl.controller.ee.ts +++ b/packages/cli/src/environments/source-control/source-control.controller.ee.ts @@ -4,17 +4,17 @@ import { Get, Post, Patch, RestController, GlobalScope } from '@/decorators'; import { sourceControlLicensedMiddleware, sourceControlLicensedAndEnabledMiddleware, -} from './middleware/sourceControlEnabledMiddleware.ee'; -import { SourceControlService } from './sourceControl.service.ee'; +} from './middleware/source-control-enabled-middleware.ee'; +import { SourceControlService } from './source-control.service.ee'; import { SourceControlRequest } from './types/requests'; -import { SourceControlPreferencesService } from './sourceControlPreferences.service.ee'; -import type { SourceControlPreferences } from './types/sourceControlPreferences'; -import type { SourceControlledFile } from './types/sourceControlledFile'; +import { SourceControlPreferencesService } from './source-control-preferences.service.ee'; +import type { SourceControlPreferences } from './types/source-control-preferences'; +import type { SourceControlledFile } from './types/source-controlled-file'; import { SOURCE_CONTROL_DEFAULT_BRANCH } from './constants'; -import type { ImportResult } from './types/importResult'; +import type { ImportResult } from './types/import-result'; import { EventService } from '@/events/event.service'; -import { getRepoType } from './sourceControlHelper.ee'; -import { SourceControlGetStatus } from './types/sourceControlGetStatus'; +import { getRepoType } from './source-control-helper.ee'; +import { SourceControlGetStatus } from './types/source-control-get-status'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; @RestController('/source-control') diff --git a/packages/cli/src/environments/sourceControl/sourceControl.service.ee.ts b/packages/cli/src/environments/source-control/source-control.service.ee.ts similarity index 96% rename from packages/cli/src/environments/sourceControl/sourceControl.service.ee.ts rename to packages/cli/src/environments/source-control/source-control.service.ee.ts index 375352e4cc78f..d264e1f75a6ed 100644 --- a/packages/cli/src/environments/sourceControl/sourceControl.service.ee.ts +++ b/packages/cli/src/environments/source-control/source-control.service.ee.ts @@ -7,29 +7,29 @@ import { getTrackingInformationFromPullResult, getVariablesPath, sourceControlFoldersExistCheck, -} from './sourceControlHelper.ee'; -import type { SourceControlPreferences } from './types/sourceControlPreferences'; +} from './source-control-helper.ee'; +import type { SourceControlPreferences } from './types/source-control-preferences'; import { SOURCE_CONTROL_DEFAULT_EMAIL, SOURCE_CONTROL_DEFAULT_NAME, SOURCE_CONTROL_README, } from './constants'; -import { SourceControlGitService } from './sourceControlGit.service.ee'; +import { SourceControlGitService } from './source-control-git.service.ee'; import type { PushResult } from 'simple-git'; -import { SourceControlExportService } from './sourceControlExport.service.ee'; -import type { ImportResult } from './types/importResult'; -import type { SourceControlPushWorkFolder } from './types/sourceControlPushWorkFolder'; -import type { SourceControllPullOptions } from './types/sourceControlPullWorkFolder'; -import type { SourceControlledFile } from './types/sourceControlledFile'; -import { SourceControlPreferencesService } from './sourceControlPreferences.service.ee'; +import { SourceControlExportService } from './source-control-export.service.ee'; +import type { ImportResult } from './types/import-result'; +import type { SourceControlPushWorkFolder } from './types/source-control-push-work-folder'; +import type { SourceControllPullOptions } from './types/source-control-pull-work-folder'; +import type { SourceControlledFile } from './types/source-controlled-file'; +import { SourceControlPreferencesService } from './source-control-preferences.service.ee'; import { writeFileSync } from 'fs'; -import { SourceControlImportService } from './sourceControlImport.service.ee'; +import { SourceControlImportService } from './source-control-import.service.ee'; import type { User } from '@db/entities/User'; -import type { SourceControlGetStatus } from './types/sourceControlGetStatus'; +import type { SourceControlGetStatus } from './types/source-control-get-status'; import type { TagEntity } from '@db/entities/TagEntity'; import type { Variables } from '@db/entities/Variables'; -import type { SourceControlWorkflowVersionId } from './types/sourceControlWorkflowVersionId'; -import type { ExportableCredential } from './types/exportableCredential'; +import type { SourceControlWorkflowVersionId } from './types/source-control-workflow-version-id'; +import type { ExportableCredential } from './types/exportable-credential'; import { EventService } from '@/events/event.service'; import { TagRepository } from '@db/repositories/tag.repository'; import { Logger } from '@/logger'; diff --git a/packages/cli/src/environments/sourceControl/types/exportResult.ts b/packages/cli/src/environments/source-control/types/export-result.ts similarity index 100% rename from packages/cli/src/environments/sourceControl/types/exportResult.ts rename to packages/cli/src/environments/source-control/types/export-result.ts diff --git a/packages/cli/src/environments/sourceControl/types/exportableCredential.ts b/packages/cli/src/environments/source-control/types/exportable-credential.ts similarity index 87% rename from packages/cli/src/environments/sourceControl/types/exportableCredential.ts rename to packages/cli/src/environments/source-control/types/exportable-credential.ts index 7ef071117f49b..a15f5b4890ec2 100644 --- a/packages/cli/src/environments/sourceControl/types/exportableCredential.ts +++ b/packages/cli/src/environments/source-control/types/exportable-credential.ts @@ -1,5 +1,5 @@ import type { ICredentialDataDecryptedObject } from 'n8n-workflow'; -import type { ResourceOwner } from './resourceOwner'; +import type { ResourceOwner } from './resource-owner'; export interface ExportableCredential { id: string; diff --git a/packages/cli/src/environments/sourceControl/types/exportableWorkflow.ts b/packages/cli/src/environments/source-control/types/exportable-workflow.ts similarity index 83% rename from packages/cli/src/environments/sourceControl/types/exportableWorkflow.ts rename to packages/cli/src/environments/source-control/types/exportable-workflow.ts index a0803bce87e27..56cec13537d0f 100644 --- a/packages/cli/src/environments/sourceControl/types/exportableWorkflow.ts +++ b/packages/cli/src/environments/source-control/types/exportable-workflow.ts @@ -1,5 +1,5 @@ import type { INode, IConnections, IWorkflowSettings } from 'n8n-workflow'; -import type { ResourceOwner } from './resourceOwner'; +import type { ResourceOwner } from './resource-owner'; export interface ExportableWorkflow { id: string; diff --git a/packages/cli/src/environments/sourceControl/types/importResult.ts b/packages/cli/src/environments/source-control/types/import-result.ts similarity index 100% rename from packages/cli/src/environments/sourceControl/types/importResult.ts rename to packages/cli/src/environments/source-control/types/import-result.ts diff --git a/packages/cli/src/environments/sourceControl/types/keyPairType.ts b/packages/cli/src/environments/source-control/types/key-pair-type.ts similarity index 100% rename from packages/cli/src/environments/sourceControl/types/keyPairType.ts rename to packages/cli/src/environments/source-control/types/key-pair-type.ts diff --git a/packages/cli/src/environments/sourceControl/types/keyPair.ts b/packages/cli/src/environments/source-control/types/key-pair.ts similarity index 100% rename from packages/cli/src/environments/sourceControl/types/keyPair.ts rename to packages/cli/src/environments/source-control/types/key-pair.ts diff --git a/packages/cli/src/environments/sourceControl/types/requests.ts b/packages/cli/src/environments/source-control/types/requests.ts similarity index 53% rename from packages/cli/src/environments/sourceControl/types/requests.ts rename to packages/cli/src/environments/source-control/types/requests.ts index e48cf94681622..5855b0af4ba04 100644 --- a/packages/cli/src/environments/sourceControl/types/requests.ts +++ b/packages/cli/src/environments/source-control/types/requests.ts @@ -1,15 +1,15 @@ import type { AuthenticatedRequest } from '@/requests'; -import type { SourceControlPreferences } from './sourceControlPreferences'; -import type { SourceControlSetBranch } from './sourceControlSetBranch'; -import type { SourceControlCommit } from './sourceControlCommit'; -import type { SourceControlStage } from './sourceControlStage'; -import type { SourceControlPush } from './sourceControlPush'; -import type { SourceControlPushWorkFolder } from './sourceControlPushWorkFolder'; -import type { SourceControlPullWorkFolder } from './sourceControlPullWorkFolder'; -import type { SourceControlDisconnect } from './sourceControlDisconnect'; -import type { SourceControlSetReadOnly } from './sourceControlSetReadOnly'; -import type { SourceControlGetStatus } from './sourceControlGetStatus'; -import type { SourceControlGenerateKeyPair } from './sourceControlGenerateKeyPair'; +import type { SourceControlPreferences } from './source-control-preferences'; +import type { SourceControlSetBranch } from './source-control-set-branch'; +import type { SourceControlCommit } from './source-control-commit'; +import type { SourceControlStage } from './source-control-stage'; +import type { SourceControlPush } from './source-control-push'; +import type { SourceControlPushWorkFolder } from './source-control-push-work-folder'; +import type { SourceControlPullWorkFolder } from './source-control-pull-work-folder'; +import type { SourceControlDisconnect } from './source-control-disconnect'; +import type { SourceControlSetReadOnly } from './source-control-set-read-only'; +import type { SourceControlGetStatus } from './source-control-get-status'; +import type { SourceControlGenerateKeyPair } from './source-control-generate-key-pair'; export declare namespace SourceControlRequest { type UpdatePreferences = AuthenticatedRequest<{}, {}, Partial, {}>; diff --git a/packages/cli/src/environments/sourceControl/types/resourceOwner.ts b/packages/cli/src/environments/source-control/types/resource-owner.ts similarity index 100% rename from packages/cli/src/environments/sourceControl/types/resourceOwner.ts rename to packages/cli/src/environments/source-control/types/resource-owner.ts diff --git a/packages/cli/src/environments/sourceControl/types/sourceControlCommit.ts b/packages/cli/src/environments/source-control/types/source-control-commit.ts similarity index 100% rename from packages/cli/src/environments/sourceControl/types/sourceControlCommit.ts rename to packages/cli/src/environments/source-control/types/source-control-commit.ts diff --git a/packages/cli/src/environments/sourceControl/types/sourceControlDisconnect.ts b/packages/cli/src/environments/source-control/types/source-control-disconnect.ts similarity index 100% rename from packages/cli/src/environments/sourceControl/types/sourceControlDisconnect.ts rename to packages/cli/src/environments/source-control/types/source-control-disconnect.ts diff --git a/packages/cli/src/environments/sourceControl/types/sourceControlGenerateKeyPair.ts b/packages/cli/src/environments/source-control/types/source-control-generate-key-pair.ts similarity index 78% rename from packages/cli/src/environments/sourceControl/types/sourceControlGenerateKeyPair.ts rename to packages/cli/src/environments/source-control/types/source-control-generate-key-pair.ts index c625b1eb2bc52..9f9093b432ba5 100644 --- a/packages/cli/src/environments/sourceControl/types/sourceControlGenerateKeyPair.ts +++ b/packages/cli/src/environments/source-control/types/source-control-generate-key-pair.ts @@ -1,5 +1,5 @@ import { IsOptional, IsString } from 'class-validator'; -import { KeyPairType } from './keyPairType'; +import { KeyPairType } from './key-pair-type'; export class SourceControlGenerateKeyPair { @IsOptional() diff --git a/packages/cli/src/environments/sourceControl/types/sourceControlGetStatus.ts b/packages/cli/src/environments/source-control/types/source-control-get-status.ts similarity index 100% rename from packages/cli/src/environments/sourceControl/types/sourceControlGetStatus.ts rename to packages/cli/src/environments/source-control/types/source-control-get-status.ts diff --git a/packages/cli/src/environments/sourceControl/types/sourceControlPreferences.ts b/packages/cli/src/environments/source-control/types/source-control-preferences.ts similarity index 96% rename from packages/cli/src/environments/sourceControl/types/sourceControlPreferences.ts rename to packages/cli/src/environments/source-control/types/source-control-preferences.ts index 9b5c3a25ca1dc..ecda479ef6cda 100644 --- a/packages/cli/src/environments/sourceControl/types/sourceControlPreferences.ts +++ b/packages/cli/src/environments/source-control/types/source-control-preferences.ts @@ -1,5 +1,5 @@ import { IsBoolean, IsHexColor, IsOptional, IsString } from 'class-validator'; -import { KeyPairType } from './keyPairType'; +import { KeyPairType } from './key-pair-type'; export class SourceControlPreferences { constructor(preferences: Partial | undefined = undefined) { diff --git a/packages/cli/src/environments/sourceControl/types/sourceControlPullWorkFolder.ts b/packages/cli/src/environments/source-control/types/source-control-pull-work-folder.ts similarity index 100% rename from packages/cli/src/environments/sourceControl/types/sourceControlPullWorkFolder.ts rename to packages/cli/src/environments/source-control/types/source-control-pull-work-folder.ts diff --git a/packages/cli/src/environments/sourceControl/types/sourceControlPushWorkFolder.ts b/packages/cli/src/environments/source-control/types/source-control-push-work-folder.ts similarity index 82% rename from packages/cli/src/environments/sourceControl/types/sourceControlPushWorkFolder.ts rename to packages/cli/src/environments/source-control/types/source-control-push-work-folder.ts index a6b23025d87aa..20299a013913d 100644 --- a/packages/cli/src/environments/sourceControl/types/sourceControlPushWorkFolder.ts +++ b/packages/cli/src/environments/source-control/types/source-control-push-work-folder.ts @@ -1,5 +1,5 @@ import { IsBoolean, IsOptional, IsString } from 'class-validator'; -import type { SourceControlledFile } from './sourceControlledFile'; +import type { SourceControlledFile } from './source-controlled-file'; export class SourceControlPushWorkFolder { @IsBoolean() diff --git a/packages/cli/src/environments/sourceControl/types/sourceControlPush.ts b/packages/cli/src/environments/source-control/types/source-control-push.ts similarity index 100% rename from packages/cli/src/environments/sourceControl/types/sourceControlPush.ts rename to packages/cli/src/environments/source-control/types/source-control-push.ts diff --git a/packages/cli/src/environments/sourceControl/types/sourceControlSetBranch.ts b/packages/cli/src/environments/source-control/types/source-control-set-branch.ts similarity index 100% rename from packages/cli/src/environments/sourceControl/types/sourceControlSetBranch.ts rename to packages/cli/src/environments/source-control/types/source-control-set-branch.ts diff --git a/packages/cli/src/environments/sourceControl/types/sourceControlSetReadOnly.ts b/packages/cli/src/environments/source-control/types/source-control-set-read-only.ts similarity index 100% rename from packages/cli/src/environments/sourceControl/types/sourceControlSetReadOnly.ts rename to packages/cli/src/environments/source-control/types/source-control-set-read-only.ts diff --git a/packages/cli/src/environments/sourceControl/types/sourceControlStage.ts b/packages/cli/src/environments/source-control/types/source-control-stage.ts similarity index 100% rename from packages/cli/src/environments/sourceControl/types/sourceControlStage.ts rename to packages/cli/src/environments/source-control/types/source-control-stage.ts diff --git a/packages/cli/src/environments/sourceControl/types/sourceControlWorkflowVersionId.ts b/packages/cli/src/environments/source-control/types/source-control-workflow-version-id.ts similarity index 100% rename from packages/cli/src/environments/sourceControl/types/sourceControlWorkflowVersionId.ts rename to packages/cli/src/environments/source-control/types/source-control-workflow-version-id.ts diff --git a/packages/cli/src/environments/sourceControl/types/sourceControlledFile.ts b/packages/cli/src/environments/source-control/types/source-controlled-file.ts similarity index 100% rename from packages/cli/src/environments/sourceControl/types/sourceControlledFile.ts rename to packages/cli/src/environments/source-control/types/source-controlled-file.ts diff --git a/packages/cli/src/environments/variables/environmentHelpers.ts b/packages/cli/src/environments/variables/environment-helpers.ts similarity index 100% rename from packages/cli/src/environments/variables/environmentHelpers.ts rename to packages/cli/src/environments/variables/environment-helpers.ts diff --git a/packages/cli/src/environments/variables/variables.service.ee.ts b/packages/cli/src/environments/variables/variables.service.ee.ts index 78a3d23fbe55d..768a9fa4565e7 100644 --- a/packages/cli/src/environments/variables/variables.service.ee.ts +++ b/packages/cli/src/environments/variables/variables.service.ee.ts @@ -1,7 +1,7 @@ import { Container, Service } from 'typedi'; import type { Variables } from '@db/entities/Variables'; import { generateNanoId } from '@db/utils/generators'; -import { canCreateNewVariable } from './environmentHelpers'; +import { canCreateNewVariable } from './environment-helpers'; import { CacheService } from '@/services/cache/cache.service'; import { VariablesRepository } from '@db/repositories/variables.repository'; import { VariableCountLimitReachedError } from '@/errors/variable-count-limit-reached.error'; diff --git a/packages/cli/src/eventbus/EventMessageClasses/AbstractEventMessageOptions.ts b/packages/cli/src/eventbus/event-message-classes/abstract-event-message-options.ts similarity index 83% rename from packages/cli/src/eventbus/EventMessageClasses/AbstractEventMessageOptions.ts rename to packages/cli/src/eventbus/event-message-classes/abstract-event-message-options.ts index 95f3ef6432a2e..7c70aa9ee5ad3 100644 --- a/packages/cli/src/eventbus/EventMessageClasses/AbstractEventMessageOptions.ts +++ b/packages/cli/src/eventbus/event-message-classes/abstract-event-message-options.ts @@ -1,7 +1,7 @@ import type { DateTime } from 'luxon'; import type { EventMessageTypeNames } from 'n8n-workflow'; import type { EventNamesTypes } from '.'; -import type { AbstractEventPayload } from './AbstractEventPayload'; +import type { AbstractEventPayload } from './abstract-event-payload'; export interface AbstractEventMessageOptions { __type?: EventMessageTypeNames; diff --git a/packages/cli/src/eventbus/EventMessageClasses/AbstractEventMessage.ts b/packages/cli/src/eventbus/event-message-classes/abstract-event-message.ts similarity index 96% rename from packages/cli/src/eventbus/EventMessageClasses/AbstractEventMessage.ts rename to packages/cli/src/eventbus/event-message-classes/abstract-event-message.ts index f71b03a413755..e74ba8c06a40a 100644 --- a/packages/cli/src/eventbus/EventMessageClasses/AbstractEventMessage.ts +++ b/packages/cli/src/eventbus/event-message-classes/abstract-event-message.ts @@ -2,8 +2,8 @@ import { DateTime } from 'luxon'; import type { EventMessageTypeNames, JsonObject } from 'n8n-workflow'; import { v4 as uuid } from 'uuid'; -import type { AbstractEventPayload } from './AbstractEventPayload'; -import type { AbstractEventMessageOptions } from './AbstractEventMessageOptions'; +import type { AbstractEventPayload } from './abstract-event-payload'; +import type { AbstractEventMessageOptions } from './abstract-event-message-options'; import type { EventNamesTypes } from '.'; function modifyUnderscoredKeys( diff --git a/packages/cli/src/eventbus/EventMessageClasses/AbstractEventPayload.ts b/packages/cli/src/eventbus/event-message-classes/abstract-event-payload.ts similarity index 100% rename from packages/cli/src/eventbus/EventMessageClasses/AbstractEventPayload.ts rename to packages/cli/src/eventbus/event-message-classes/abstract-event-payload.ts diff --git a/packages/cli/src/eventbus/EventMessageClasses/EventMessageAiNode.ts b/packages/cli/src/eventbus/event-message-classes/event-message-ai-node.ts similarity index 87% rename from packages/cli/src/eventbus/EventMessageClasses/EventMessageAiNode.ts rename to packages/cli/src/eventbus/event-message-classes/event-message-ai-node.ts index 44d9feafbde14..b1ee96099373c 100644 --- a/packages/cli/src/eventbus/EventMessageClasses/EventMessageAiNode.ts +++ b/packages/cli/src/eventbus/event-message-classes/event-message-ai-node.ts @@ -1,8 +1,8 @@ -import { AbstractEventMessage, isEventMessageOptionsWithType } from './AbstractEventMessage'; +import { AbstractEventMessage, isEventMessageOptionsWithType } from './abstract-event-message'; import type { EventNamesAiNodesType, JsonObject } from 'n8n-workflow'; import { EventMessageTypeNames } from 'n8n-workflow'; -import type { AbstractEventMessageOptions } from './AbstractEventMessageOptions'; -import type { AbstractEventPayload } from './AbstractEventPayload'; +import type { AbstractEventMessageOptions } from './abstract-event-message-options'; +import type { AbstractEventPayload } from './abstract-event-payload'; // -------------------------------------- // EventMessage class for Node events diff --git a/packages/cli/src/eventbus/EventMessageClasses/EventMessageAudit.ts b/packages/cli/src/eventbus/event-message-classes/event-message-audit.ts similarity index 88% rename from packages/cli/src/eventbus/EventMessageClasses/EventMessageAudit.ts rename to packages/cli/src/eventbus/event-message-classes/event-message-audit.ts index 737a1ebf0a2e1..4ae0fa485d85b 100644 --- a/packages/cli/src/eventbus/EventMessageClasses/EventMessageAudit.ts +++ b/packages/cli/src/eventbus/event-message-classes/event-message-audit.ts @@ -1,8 +1,8 @@ -import { AbstractEventMessage, isEventMessageOptionsWithType } from './AbstractEventMessage'; +import { AbstractEventMessage, isEventMessageOptionsWithType } from './abstract-event-message'; import { EventMessageTypeNames } from 'n8n-workflow'; import type { JsonObject, JsonValue } from 'n8n-workflow'; -import type { AbstractEventPayload } from './AbstractEventPayload'; -import type { AbstractEventMessageOptions } from './AbstractEventMessageOptions'; +import type { AbstractEventPayload } from './abstract-event-payload'; +import type { AbstractEventMessageOptions } from './abstract-event-message-options'; import type { EventNamesAuditType } from '.'; // -------------------------------------- diff --git a/packages/cli/src/eventbus/EventMessageClasses/EventMessageConfirm.ts b/packages/cli/src/eventbus/event-message-classes/event-message-confirm.ts similarity index 100% rename from packages/cli/src/eventbus/EventMessageClasses/EventMessageConfirm.ts rename to packages/cli/src/eventbus/event-message-classes/event-message-confirm.ts diff --git a/packages/cli/src/eventbus/EventMessageClasses/EventMessageExecution.ts b/packages/cli/src/eventbus/event-message-classes/event-message-execution.ts similarity index 86% rename from packages/cli/src/eventbus/EventMessageClasses/EventMessageExecution.ts rename to packages/cli/src/eventbus/event-message-classes/event-message-execution.ts index 9a8864c7f0b76..e311e5354fce7 100644 --- a/packages/cli/src/eventbus/EventMessageClasses/EventMessageExecution.ts +++ b/packages/cli/src/eventbus/event-message-classes/event-message-execution.ts @@ -1,8 +1,8 @@ -import { AbstractEventMessage, isEventMessageOptionsWithType } from './AbstractEventMessage'; +import { AbstractEventMessage, isEventMessageOptionsWithType } from './abstract-event-message'; import type { JsonObject } from 'n8n-workflow'; import { EventMessageTypeNames } from 'n8n-workflow'; -import type { AbstractEventMessageOptions } from './AbstractEventMessageOptions'; -import type { AbstractEventPayload } from './AbstractEventPayload'; +import type { AbstractEventMessageOptions } from './abstract-event-message-options'; +import type { AbstractEventPayload } from './abstract-event-payload'; import type { EventNamesExecutionType } from '.'; export interface EventPayloadExecution extends AbstractEventPayload { diff --git a/packages/cli/src/eventbus/EventMessageClasses/EventMessageGeneric.ts b/packages/cli/src/eventbus/event-message-classes/event-message-generic.ts similarity index 85% rename from packages/cli/src/eventbus/EventMessageClasses/EventMessageGeneric.ts rename to packages/cli/src/eventbus/event-message-classes/event-message-generic.ts index 0e49b33b4fd58..14d8bfb345e23 100644 --- a/packages/cli/src/eventbus/EventMessageClasses/EventMessageGeneric.ts +++ b/packages/cli/src/eventbus/event-message-classes/event-message-generic.ts @@ -1,8 +1,8 @@ import type { JsonObject } from 'n8n-workflow'; import { EventMessageTypeNames } from 'n8n-workflow'; -import { AbstractEventMessage, isEventMessageOptionsWithType } from './AbstractEventMessage'; -import type { AbstractEventPayload } from './AbstractEventPayload'; -import type { AbstractEventMessageOptions } from './AbstractEventMessageOptions'; +import { AbstractEventMessage, isEventMessageOptionsWithType } from './abstract-event-message'; +import type { AbstractEventPayload } from './abstract-event-payload'; +import type { AbstractEventMessageOptions } from './abstract-event-message-options'; export const eventMessageGenericDestinationTestEvent = 'n8n.destination.test'; diff --git a/packages/cli/src/eventbus/EventMessageClasses/EventMessageNode.ts b/packages/cli/src/eventbus/event-message-classes/event-message-node.ts similarity index 87% rename from packages/cli/src/eventbus/EventMessageClasses/EventMessageNode.ts rename to packages/cli/src/eventbus/event-message-classes/event-message-node.ts index d9dda95e27b89..da36c2456cd78 100644 --- a/packages/cli/src/eventbus/EventMessageClasses/EventMessageNode.ts +++ b/packages/cli/src/eventbus/event-message-classes/event-message-node.ts @@ -1,8 +1,8 @@ -import { AbstractEventMessage, isEventMessageOptionsWithType } from './AbstractEventMessage'; +import { AbstractEventMessage, isEventMessageOptionsWithType } from './abstract-event-message'; import type { JsonObject } from 'n8n-workflow'; import { EventMessageTypeNames } from 'n8n-workflow'; -import type { AbstractEventMessageOptions } from './AbstractEventMessageOptions'; -import type { AbstractEventPayload } from './AbstractEventPayload'; +import type { AbstractEventMessageOptions } from './abstract-event-message-options'; +import type { AbstractEventPayload } from './abstract-event-payload'; import type { EventNamesNodeType } from '.'; // -------------------------------------- diff --git a/packages/cli/src/eventbus/EventMessageClasses/EventMessageWorkflow.ts b/packages/cli/src/eventbus/event-message-classes/event-message-workflow.ts similarity index 88% rename from packages/cli/src/eventbus/EventMessageClasses/EventMessageWorkflow.ts rename to packages/cli/src/eventbus/event-message-classes/event-message-workflow.ts index 146e0ffcd3229..22c0fc6b5171a 100644 --- a/packages/cli/src/eventbus/EventMessageClasses/EventMessageWorkflow.ts +++ b/packages/cli/src/eventbus/event-message-classes/event-message-workflow.ts @@ -1,8 +1,8 @@ -import { AbstractEventMessage, isEventMessageOptionsWithType } from './AbstractEventMessage'; +import { AbstractEventMessage, isEventMessageOptionsWithType } from './abstract-event-message'; import type { IWorkflowBase, JsonObject } from 'n8n-workflow'; import { EventMessageTypeNames } from 'n8n-workflow'; -import type { AbstractEventMessageOptions } from './AbstractEventMessageOptions'; -import type { AbstractEventPayload } from './AbstractEventPayload'; +import type { AbstractEventMessageOptions } from './abstract-event-message-options'; +import type { AbstractEventPayload } from './abstract-event-payload'; import type { IExecutionBase } from '@/Interfaces'; import type { EventNamesWorkflowType } from '.'; diff --git a/packages/cli/src/eventbus/EventMessageClasses/index.ts b/packages/cli/src/eventbus/event-message-classes/index.ts similarity index 84% rename from packages/cli/src/eventbus/EventMessageClasses/index.ts rename to packages/cli/src/eventbus/event-message-classes/index.ts index 2e4d78560d9da..8e0f367571819 100644 --- a/packages/cli/src/eventbus/EventMessageClasses/index.ts +++ b/packages/cli/src/eventbus/event-message-classes/index.ts @@ -1,9 +1,9 @@ -import type { EventMessageAiNode } from './EventMessageAiNode'; -import type { EventMessageAudit } from './EventMessageAudit'; -import type { EventMessageExecution } from './EventMessageExecution'; -import type { EventMessageGeneric } from './EventMessageGeneric'; -import type { EventMessageNode } from './EventMessageNode'; -import type { EventMessageWorkflow } from './EventMessageWorkflow'; +import type { EventMessageAiNode } from './event-message-ai-node'; +import type { EventMessageAudit } from './event-message-audit'; +import type { EventMessageExecution } from './event-message-execution'; +import type { EventMessageGeneric } from './event-message-generic'; +import type { EventMessageNode } from './event-message-node'; +import type { EventMessageWorkflow } from './event-message-workflow'; import { eventNamesAiNodes, type EventNamesAiNodesType } from 'n8n-workflow'; export const eventNamesWorkflow = [ diff --git a/packages/cli/src/eventbus/eventBus.controller.ts b/packages/cli/src/eventbus/eventBus.controller.ts index 419c4055aa264..d9922b482e80f 100644 --- a/packages/cli/src/eventbus/eventBus.controller.ts +++ b/packages/cli/src/eventbus/eventBus.controller.ts @@ -1,4 +1,4 @@ -import { eventNamesAll } from './EventMessageClasses'; +import { eventNamesAll } from './event-message-classes'; import express from 'express'; import type { MessageEventBusDestinationWebhookOptions, @@ -10,17 +10,17 @@ import { RestController, Get, Post, Delete, GlobalScope, Licensed } from '@/deco import { AuthenticatedRequest } from '@/requests'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; -import { MessageEventBus } from './MessageEventBus/MessageEventBus'; +import { MessageEventBus } from './message-event-bus/message-event-bus'; import { isMessageEventBusDestinationSentryOptions, MessageEventBusDestinationSentry, -} from './MessageEventBusDestination/MessageEventBusDestinationSentry.ee'; +} from './message-event-bus-destination/message-event-bus-destination-sentry.ee'; import { isMessageEventBusDestinationSyslogOptions, MessageEventBusDestinationSyslog, -} from './MessageEventBusDestination/MessageEventBusDestinationSyslog.ee'; -import { MessageEventBusDestinationWebhook } from './MessageEventBusDestination/MessageEventBusDestinationWebhook.ee'; -import type { MessageEventBusDestination } from './MessageEventBusDestination/MessageEventBusDestination.ee'; +} from './message-event-bus-destination/message-event-bus-destination-syslog.ee'; +import { MessageEventBusDestinationWebhook } from './message-event-bus-destination/message-event-bus-destination-webhook.ee'; +import type { MessageEventBusDestination } from './message-event-bus-destination/message-event-bus-destination.ee'; const isWithIdString = (candidate: unknown): candidate is { id: string } => { const o = candidate as { id: string }; diff --git a/packages/cli/src/eventbus/index.ts b/packages/cli/src/eventbus/index.ts index fd3658c0a4a13..8f73005ba5d65 100644 --- a/packages/cli/src/eventbus/index.ts +++ b/packages/cli/src/eventbus/index.ts @@ -1,2 +1,2 @@ -export { EventMessageTypes } from './EventMessageClasses'; -export { EventPayloadWorkflow } from './EventMessageClasses/EventMessageWorkflow'; +export { EventMessageTypes } from './event-message-classes'; +export { EventPayloadWorkflow } from './event-message-classes/event-message-workflow'; diff --git a/packages/cli/src/eventbus/MessageEventBusDestination/MessageEventBusDestinationFromDb.ts b/packages/cli/src/eventbus/message-event-bus-destination/message-event-bus-destination-from-db.ts similarity index 69% rename from packages/cli/src/eventbus/MessageEventBusDestination/MessageEventBusDestinationFromDb.ts rename to packages/cli/src/eventbus/message-event-bus-destination/message-event-bus-destination-from-db.ts index 886d1e35dd9ba..ebc5ec7a4bc08 100644 --- a/packages/cli/src/eventbus/MessageEventBusDestination/MessageEventBusDestinationFromDb.ts +++ b/packages/cli/src/eventbus/message-event-bus-destination/message-event-bus-destination-from-db.ts @@ -1,10 +1,10 @@ import { MessageEventBusDestinationTypeNames } from 'n8n-workflow'; import type { EventDestinations } from '@db/entities/EventDestinations'; -import type { MessageEventBus } from '../MessageEventBus/MessageEventBus'; -import type { MessageEventBusDestination } from './MessageEventBusDestination.ee'; -import { MessageEventBusDestinationSentry } from './MessageEventBusDestinationSentry.ee'; -import { MessageEventBusDestinationSyslog } from './MessageEventBusDestinationSyslog.ee'; -import { MessageEventBusDestinationWebhook } from './MessageEventBusDestinationWebhook.ee'; +import type { MessageEventBus } from '../message-event-bus/message-event-bus'; +import type { MessageEventBusDestination } from './message-event-bus-destination.ee'; +import { MessageEventBusDestinationSentry } from './message-event-bus-destination-sentry.ee'; +import { MessageEventBusDestinationSyslog } from './message-event-bus-destination-syslog.ee'; +import { MessageEventBusDestinationWebhook } from './message-event-bus-destination-webhook.ee'; import { Container } from 'typedi'; import { Logger } from '@/logger'; diff --git a/packages/cli/src/eventbus/MessageEventBusDestination/MessageEventBusDestinationSentry.ee.ts b/packages/cli/src/eventbus/message-event-bus-destination/message-event-bus-destination-sentry.ee.ts similarity index 92% rename from packages/cli/src/eventbus/MessageEventBusDestination/MessageEventBusDestinationSentry.ee.ts rename to packages/cli/src/eventbus/message-event-bus-destination/message-event-bus-destination-sentry.ee.ts index 25633c3ac628a..d376d116102cf 100644 --- a/packages/cli/src/eventbus/MessageEventBusDestination/MessageEventBusDestinationSentry.ee.ts +++ b/packages/cli/src/eventbus/message-event-bus-destination/message-event-bus-destination-sentry.ee.ts @@ -1,15 +1,15 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ -import { MessageEventBusDestination } from './MessageEventBusDestination.ee'; +import { MessageEventBusDestination } from './message-event-bus-destination.ee'; import * as Sentry from '@sentry/node'; import { MessageEventBusDestinationTypeNames } from 'n8n-workflow'; import type { MessageEventBusDestinationOptions, MessageEventBusDestinationSentryOptions, } from 'n8n-workflow'; -import { eventMessageGenericDestinationTestEvent } from '../EventMessageClasses/EventMessageGeneric'; +import { eventMessageGenericDestinationTestEvent } from '../event-message-classes/event-message-generic'; import { N8N_VERSION } from '@/constants'; -import type { MessageEventBus, MessageWithCallback } from '../MessageEventBus/MessageEventBus'; +import type { MessageEventBus, MessageWithCallback } from '../message-event-bus/message-event-bus'; export const isMessageEventBusDestinationSentryOptions = ( candidate: unknown, diff --git a/packages/cli/src/eventbus/MessageEventBusDestination/MessageEventBusDestinationSyslog.ee.ts b/packages/cli/src/eventbus/message-event-bus-destination/message-event-bus-destination-syslog.ee.ts similarity index 93% rename from packages/cli/src/eventbus/MessageEventBusDestination/MessageEventBusDestinationSyslog.ee.ts rename to packages/cli/src/eventbus/message-event-bus-destination/message-event-bus-destination-syslog.ee.ts index 8d9b7438293e3..a16718086d1b2 100644 --- a/packages/cli/src/eventbus/MessageEventBusDestination/MessageEventBusDestinationSyslog.ee.ts +++ b/packages/cli/src/eventbus/message-event-bus-destination/message-event-bus-destination-syslog.ee.ts @@ -6,9 +6,9 @@ import type { MessageEventBusDestinationSyslogOptions, } from 'n8n-workflow'; import { MessageEventBusDestinationTypeNames } from 'n8n-workflow'; -import { MessageEventBusDestination } from './MessageEventBusDestination.ee'; -import { eventMessageGenericDestinationTestEvent } from '../EventMessageClasses/EventMessageGeneric'; -import type { MessageEventBus, MessageWithCallback } from '../MessageEventBus/MessageEventBus'; +import { MessageEventBusDestination } from './message-event-bus-destination.ee'; +import { eventMessageGenericDestinationTestEvent } from '../event-message-classes/event-message-generic'; +import type { MessageEventBus, MessageWithCallback } from '../message-event-bus/message-event-bus'; import Container from 'typedi'; import { Logger } from '@/logger'; export const isMessageEventBusDestinationSyslogOptions = ( diff --git a/packages/cli/src/eventbus/MessageEventBusDestination/MessageEventBusDestinationWebhook.ee.ts b/packages/cli/src/eventbus/message-event-bus-destination/message-event-bus-destination-webhook.ee.ts similarity index 97% rename from packages/cli/src/eventbus/MessageEventBusDestination/MessageEventBusDestinationWebhook.ee.ts rename to packages/cli/src/eventbus/message-event-bus-destination/message-event-bus-destination-webhook.ee.ts index 552185495254c..01cffac93ae2a 100644 --- a/packages/cli/src/eventbus/MessageEventBusDestination/MessageEventBusDestinationWebhook.ee.ts +++ b/packages/cli/src/eventbus/message-event-bus-destination/message-event-bus-destination-webhook.ee.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -import { MessageEventBusDestination } from './MessageEventBusDestination.ee'; +import { MessageEventBusDestination } from './message-event-bus-destination.ee'; import axios from 'axios'; import type { AxiosRequestConfig, Method } from 'axios'; import { jsonParse, MessageEventBusDestinationTypeNames } from 'n8n-workflow'; @@ -13,8 +13,8 @@ import type { } from 'n8n-workflow'; import { CredentialsHelper } from '@/credentials-helper'; import { Agent as HTTPSAgent } from 'https'; -import { eventMessageGenericDestinationTestEvent } from '../EventMessageClasses/EventMessageGeneric'; -import type { MessageEventBus, MessageWithCallback } from '../MessageEventBus/MessageEventBus'; +import { eventMessageGenericDestinationTestEvent } from '../event-message-classes/event-message-generic'; +import type { MessageEventBus, MessageWithCallback } from '../message-event-bus/message-event-bus'; import * as SecretsHelpers from '@/external-secrets/external-secrets-helper.ee'; import Container from 'typedi'; diff --git a/packages/cli/src/eventbus/MessageEventBusDestination/MessageEventBusDestination.ee.ts b/packages/cli/src/eventbus/message-event-bus-destination/message-event-bus-destination.ee.ts similarity index 91% rename from packages/cli/src/eventbus/MessageEventBusDestination/MessageEventBusDestination.ee.ts rename to packages/cli/src/eventbus/message-event-bus-destination/message-event-bus-destination.ee.ts index 3cd86f4368aff..1b9085e9d007a 100644 --- a/packages/cli/src/eventbus/MessageEventBusDestination/MessageEventBusDestination.ee.ts +++ b/packages/cli/src/eventbus/message-event-bus-destination/message-event-bus-destination.ee.ts @@ -3,10 +3,10 @@ import { Container } from 'typedi'; import type { INodeCredentials, MessageEventBusDestinationOptions } from 'n8n-workflow'; import { MessageEventBusDestinationTypeNames } from 'n8n-workflow'; import { Logger } from '@/logger'; -import type { AbstractEventMessage } from '../EventMessageClasses/AbstractEventMessage'; -import type { EventMessageTypes } from '../EventMessageClasses'; -import type { EventMessageConfirmSource } from '../EventMessageClasses/EventMessageConfirm'; -import type { MessageEventBus, MessageWithCallback } from '../MessageEventBus/MessageEventBus'; +import type { AbstractEventMessage } from '../event-message-classes/abstract-event-message'; +import type { EventMessageTypes } from '../event-message-classes'; +import type { EventMessageConfirmSource } from '../event-message-classes/event-message-confirm'; +import type { MessageEventBus, MessageWithCallback } from '../message-event-bus/message-event-bus'; import { EventDestinationsRepository } from '@db/repositories/eventDestinations.repository'; import { License } from '@/license'; diff --git a/packages/cli/src/eventbus/MessageEventBusWriter/MessageEventBusLogWriterWorker.ts b/packages/cli/src/eventbus/message-event-bus-writer/message-event-bus-log-writer-worker.ts similarity index 97% rename from packages/cli/src/eventbus/MessageEventBusWriter/MessageEventBusLogWriterWorker.ts rename to packages/cli/src/eventbus/message-event-bus-writer/message-event-bus-log-writer-worker.ts index 69d2e8ce26f73..308d4508c1c5c 100644 --- a/packages/cli/src/eventbus/MessageEventBusWriter/MessageEventBusLogWriterWorker.ts +++ b/packages/cli/src/eventbus/message-event-bus-writer/message-event-bus-log-writer-worker.ts @@ -1,7 +1,7 @@ import { appendFileSync, existsSync, rmSync, renameSync, openSync, closeSync } from 'fs'; import { stat } from 'fs/promises'; import { isMainThread, parentPort } from 'worker_threads'; -import type { MessageEventBusLogWriterOptions } from './MessageEventBusLogWriter'; +import type { MessageEventBusLogWriterOptions } from './message-event-bus-log-writer'; let logFileBasePath = ''; let loggingPaused = true; diff --git a/packages/cli/src/eventbus/MessageEventBusWriter/MessageEventBusLogWriter.ts b/packages/cli/src/eventbus/message-event-bus-writer/message-event-bus-log-writer.ts similarity index 89% rename from packages/cli/src/eventbus/MessageEventBusWriter/MessageEventBusLogWriter.ts rename to packages/cli/src/eventbus/message-event-bus-writer/message-event-bus-log-writer.ts index 617dacd2830ce..fd6eb1db38598 100644 --- a/packages/cli/src/eventbus/MessageEventBusWriter/MessageEventBusLogWriter.ts +++ b/packages/cli/src/eventbus/message-event-bus-writer/message-event-bus-log-writer.ts @@ -1,29 +1,29 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ -import { isEventMessageOptions } from '../EventMessageClasses/AbstractEventMessage'; +import { isEventMessageOptions } from '../event-message-classes/abstract-event-message'; import { InstanceSettings } from 'n8n-core'; import path, { parse } from 'path'; import { Worker } from 'worker_threads'; import { createReadStream, existsSync, rmSync } from 'fs'; import readline from 'readline'; import remove from 'lodash/remove'; -import type { EventMessageGenericOptions } from '../EventMessageClasses/EventMessageGeneric'; -import { EventMessageGeneric } from '../EventMessageClasses/EventMessageGeneric'; -import type { AbstractEventMessageOptions } from '../EventMessageClasses/AbstractEventMessageOptions'; -import type { EventMessageWorkflowOptions } from '../EventMessageClasses/EventMessageWorkflow'; -import { EventMessageWorkflow } from '../EventMessageClasses/EventMessageWorkflow'; +import type { EventMessageGenericOptions } from '../event-message-classes/event-message-generic'; +import { EventMessageGeneric } from '../event-message-classes/event-message-generic'; +import type { AbstractEventMessageOptions } from '../event-message-classes/abstract-event-message-options'; +import type { EventMessageWorkflowOptions } from '../event-message-classes/event-message-workflow'; +import { EventMessageWorkflow } from '../event-message-classes/event-message-workflow'; import { EventMessageTypeNames, jsonParse } from 'n8n-workflow'; -import type { EventMessageAuditOptions } from '../EventMessageClasses/EventMessageAudit'; -import { EventMessageAudit } from '../EventMessageClasses/EventMessageAudit'; -import type { EventMessageNodeOptions } from '../EventMessageClasses/EventMessageNode'; -import { EventMessageNode } from '../EventMessageClasses/EventMessageNode'; -import type { EventMessageReturnMode } from '../MessageEventBus/MessageEventBus'; -import type { EventMessageTypes } from '../EventMessageClasses'; -import type { EventMessageConfirmSource } from '../EventMessageClasses/EventMessageConfirm'; +import type { EventMessageAuditOptions } from '../event-message-classes/event-message-audit'; +import { EventMessageAudit } from '../event-message-classes/event-message-audit'; +import type { EventMessageNodeOptions } from '../event-message-classes/event-message-node'; +import { EventMessageNode } from '../event-message-classes/event-message-node'; +import type { EventMessageReturnMode } from '../message-event-bus/message-event-bus'; +import type { EventMessageTypes } from '../event-message-classes'; +import type { EventMessageConfirmSource } from '../event-message-classes/event-message-confirm'; import { EventMessageConfirm, isEventMessageConfirm, -} from '../EventMessageClasses/EventMessageConfirm'; +} from '../event-message-classes/event-message-confirm'; import { once as eventOnce } from 'events'; import { inTest } from '@/constants'; import { Logger } from '@/logger'; @@ -139,9 +139,10 @@ export class MessageEventBusLogWriter { const parsedName = parse(__filename); let workerFileName; if (inTest) { - workerFileName = './dist/eventbus/MessageEventBusWriter/MessageEventBusLogWriterWorker.js'; + workerFileName = + './dist/eventbus/message-event-bus-writer/message-event-bus-log-writer-worker.js'; } else { - workerFileName = path.join(parsedName.dir, `${parsedName.name}Worker${parsedName.ext}`); + workerFileName = path.join(parsedName.dir, `${parsedName.name}-worker${parsedName.ext}`); } this._worker = new Worker(workerFileName); if (this.worker) { diff --git a/packages/cli/src/eventbus/MessageEventBus/MessageEventBus.ts b/packages/cli/src/eventbus/message-event-bus/message-event-bus.ts similarity index 91% rename from packages/cli/src/eventbus/MessageEventBus/MessageEventBus.ts rename to packages/cli/src/eventbus/message-event-bus/message-event-bus.ts index 38ec4e8dc72c7..47d4d327d46f4 100644 --- a/packages/cli/src/eventbus/MessageEventBus/MessageEventBus.ts +++ b/packages/cli/src/eventbus/message-event-bus/message-event-bus.ts @@ -14,29 +14,29 @@ import { WorkflowRepository } from '@db/repositories/workflow.repository'; import { OrchestrationService } from '@/services/orchestration.service'; import { Logger } from '@/logger'; -import type { EventMessageTypes } from '../EventMessageClasses/'; -import type { MessageEventBusDestination } from '../MessageEventBusDestination/MessageEventBusDestination.ee'; -import { MessageEventBusLogWriter } from '../MessageEventBusWriter/MessageEventBusLogWriter'; -import { messageEventBusDestinationFromDb } from '../MessageEventBusDestination/MessageEventBusDestinationFromDb'; -import type { EventMessageConfirmSource } from '../EventMessageClasses/EventMessageConfirm'; -import type { EventMessageAuditOptions } from '../EventMessageClasses/EventMessageAudit'; -import { EventMessageAudit } from '../EventMessageClasses/EventMessageAudit'; -import type { EventMessageWorkflowOptions } from '../EventMessageClasses/EventMessageWorkflow'; -import { EventMessageWorkflow } from '../EventMessageClasses/EventMessageWorkflow'; -import type { EventMessageNodeOptions } from '../EventMessageClasses/EventMessageNode'; -import { EventMessageNode } from '../EventMessageClasses/EventMessageNode'; +import type { EventMessageTypes } from '../event-message-classes/'; +import type { MessageEventBusDestination } from '../message-event-bus-destination/message-event-bus-destination.ee'; +import { MessageEventBusLogWriter } from '../message-event-bus-writer/message-event-bus-log-writer'; +import { messageEventBusDestinationFromDb } from '../message-event-bus-destination/message-event-bus-destination-from-db'; +import type { EventMessageConfirmSource } from '../event-message-classes/event-message-confirm'; +import type { EventMessageAuditOptions } from '../event-message-classes/event-message-audit'; +import { EventMessageAudit } from '../event-message-classes/event-message-audit'; +import type { EventMessageWorkflowOptions } from '../event-message-classes/event-message-workflow'; +import { EventMessageWorkflow } from '../event-message-classes/event-message-workflow'; +import type { EventMessageNodeOptions } from '../event-message-classes/event-message-node'; +import { EventMessageNode } from '../event-message-classes/event-message-node'; import { EventMessageGeneric, eventMessageGenericDestinationTestEvent, -} from '../EventMessageClasses/EventMessageGeneric'; +} from '../event-message-classes/event-message-generic'; import { ExecutionRecoveryService } from '../../executions/execution-recovery.service'; import { EventMessageAiNode, type EventMessageAiNodeOptions, -} from '../EventMessageClasses/EventMessageAiNode'; +} from '../event-message-classes/event-message-ai-node'; import { License } from '@/license'; -import type { EventMessageExecutionOptions } from '../EventMessageClasses/EventMessageExecution'; -import { EventMessageExecution } from '../EventMessageClasses/EventMessageExecution'; +import type { EventMessageExecutionOptions } from '../event-message-classes/event-message-execution'; +import { EventMessageExecution } from '../event-message-classes/event-message-execution'; import { GlobalConfig } from '@n8n/config'; export type EventMessageReturnMode = 'sent' | 'unsent' | 'all' | 'unfinished'; diff --git a/packages/cli/src/events/__tests__/log-streaming-event-relay.test.ts b/packages/cli/src/events/__tests__/log-streaming-event-relay.test.ts index 720e2d494c332..3a30738e63bc3 100644 --- a/packages/cli/src/events/__tests__/log-streaming-event-relay.test.ts +++ b/packages/cli/src/events/__tests__/log-streaming-event-relay.test.ts @@ -3,7 +3,7 @@ import { LogStreamingEventRelay } from '@/events/log-streaming-event-relay'; import { EventService } from '@/events/event.service'; import type { INode, IRun, IWorkflowBase } from 'n8n-workflow'; import type { IWorkflowDb } from '@/Interfaces'; -import type { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus'; +import type { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus'; import type { RelayEventMap } from '@/events/relay-event-map'; describe('LogStreamingEventRelay', () => { diff --git a/packages/cli/src/events/log-streaming-event-relay.ts b/packages/cli/src/events/log-streaming-event-relay.ts index 2012775d91977..db704128eace2 100644 --- a/packages/cli/src/events/log-streaming-event-relay.ts +++ b/packages/cli/src/events/log-streaming-event-relay.ts @@ -1,5 +1,5 @@ import { Service } from 'typedi'; -import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus'; +import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus'; import { Redactable } from '@/decorators/redactable'; import { EventRelay } from '@/events/event-relay'; import type { RelayEventMap } from '@/events/relay-event-map'; diff --git a/packages/cli/src/executions/__tests__/execution-recovery.service.test.ts b/packages/cli/src/executions/__tests__/execution-recovery.service.test.ts index f72c81a3cad53..a6e8a01aaa262 100644 --- a/packages/cli/src/executions/__tests__/execution-recovery.service.test.ts +++ b/packages/cli/src/executions/__tests__/execution-recovery.service.test.ts @@ -15,11 +15,11 @@ import { Push } from '@/push'; import { ARTIFICIAL_TASK_DATA } from '@/constants'; import { NodeCrashedError } from '@/errors/node-crashed.error'; import { WorkflowCrashedError } from '@/errors/workflow-crashed.error'; -import { EventMessageNode } from '@/eventbus/EventMessageClasses/EventMessageNode'; +import { EventMessageNode } from '@/eventbus/event-message-classes/event-message-node'; import { IN_PROGRESS_EXECUTION_DATA, OOM_WORKFLOW } from './constants'; import { setupMessages } from './utils'; -import type { EventMessageTypes as EventMessage } from '@/eventbus/EventMessageClasses'; +import type { EventMessageTypes as EventMessage } from '@/eventbus/event-message-classes'; describe('ExecutionRecoveryService', () => { const push = mockInstance(Push); diff --git a/packages/cli/src/executions/__tests__/execution.service.test.ts b/packages/cli/src/executions/__tests__/execution.service.test.ts index 99370c1e1b77e..dfdb4099bd90e 100644 --- a/packages/cli/src/executions/__tests__/execution.service.test.ts +++ b/packages/cli/src/executions/__tests__/execution.service.test.ts @@ -11,7 +11,7 @@ import type { WaitTracker } from '@/wait-tracker'; import type { ExecutionRepository } from '@/databases/repositories/execution.repository'; import type { ExecutionRequest } from '@/executions/execution.types'; import type { ConcurrencyControlService } from '@/concurrency/concurrency-control.service'; -import type { Job } from '@/scaling/types'; +import type { Job } from '@/scaling/scaling.types'; import { mockInstance } from '@test/mocking'; describe('ExecutionService', () => { diff --git a/packages/cli/src/executions/__tests__/utils.ts b/packages/cli/src/executions/__tests__/utils.ts index 6ffb9109ecb40..7c5ee85b3e5d8 100644 --- a/packages/cli/src/executions/__tests__/utils.ts +++ b/packages/cli/src/executions/__tests__/utils.ts @@ -1,6 +1,6 @@ -import type { EventMessageTypes as EventMessage } from '@/eventbus/EventMessageClasses'; -import { EventMessageNode } from '@/eventbus/EventMessageClasses/EventMessageNode'; -import { EventMessageWorkflow } from '@/eventbus/EventMessageClasses/EventMessageWorkflow'; +import type { EventMessageTypes as EventMessage } from '@/eventbus/event-message-classes'; +import { EventMessageNode } from '@/eventbus/event-message-classes/event-message-node'; +import { EventMessageWorkflow } from '@/eventbus/event-message-classes/event-message-workflow'; export const setupMessages = (executionId: string, workflowName: string): EventMessage[] => { return [ diff --git a/packages/cli/src/executions/execution-recovery.service.ts b/packages/cli/src/executions/execution-recovery.service.ts index ffe4e04a4513d..1c8fe7d639035 100644 --- a/packages/cli/src/executions/execution-recovery.service.ts +++ b/packages/cli/src/executions/execution-recovery.service.ts @@ -6,7 +6,7 @@ import { getWorkflowHooksMain } from '@/workflow-execute-additional-data'; // @T import type { DateTime } from 'luxon'; import type { IRun, ITaskData } from 'n8n-workflow'; import { InstanceSettings } from 'n8n-core'; -import type { EventMessageTypes } from '../eventbus/EventMessageClasses'; +import type { EventMessageTypes } from '../eventbus/event-message-classes'; import type { IExecutionResponse } from '@/Interfaces'; import { NodeCrashedError } from '@/errors/node-crashed.error'; import { WorkflowCrashedError } from '@/errors/workflow-crashed.error'; diff --git a/packages/cli/src/internal-hooks.ts b/packages/cli/src/internal-hooks.ts index b3d45b67c0acd..378a01ecaa9ba 100644 --- a/packages/cli/src/internal-hooks.ts +++ b/packages/cli/src/internal-hooks.ts @@ -1,6 +1,6 @@ import { Service } from 'typedi'; import { Telemetry } from '@/telemetry'; -import { MessageEventBus } from './eventbus/MessageEventBus/MessageEventBus'; +import { MessageEventBus } from './eventbus/message-event-bus/message-event-bus'; /** * @deprecated Do not add to this class. It will be removed once we remove diff --git a/packages/cli/src/metrics/__tests__/prometheus-metrics.service.test.ts b/packages/cli/src/metrics/__tests__/prometheus-metrics.service.test.ts index 219170ac085a8..91f99f91b041a 100644 --- a/packages/cli/src/metrics/__tests__/prometheus-metrics.service.test.ts +++ b/packages/cli/src/metrics/__tests__/prometheus-metrics.service.test.ts @@ -4,7 +4,7 @@ import promBundle from 'express-prom-bundle'; import { mock } from 'jest-mock-extended'; import { PrometheusMetricsService } from '../prometheus-metrics.service'; import type express from 'express'; -import type { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus'; +import type { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus'; import { mockInstance } from '@test/mocking'; import { GlobalConfig } from '@n8n/config'; diff --git a/packages/cli/src/metrics/prometheus-metrics.service.ts b/packages/cli/src/metrics/prometheus-metrics.service.ts index 1444f6f694bbd..32522808fc243 100644 --- a/packages/cli/src/metrics/prometheus-metrics.service.ts +++ b/packages/cli/src/metrics/prometheus-metrics.service.ts @@ -6,7 +6,7 @@ import semverParse from 'semver/functions/parse'; import { Service } from 'typedi'; import { CacheService } from '@/services/cache/cache.service'; -import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus'; +import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus'; import { EventMessageTypeNames } from 'n8n-workflow'; import type { EventMessageTypes } from '@/eventbus'; import type { Includes, MetricCategory, MetricLabel } from './types'; diff --git a/packages/cli/src/scaling/__tests__/scaling.service.test.ts b/packages/cli/src/scaling/__tests__/scaling.service.test.ts index adbf5ebde2f0e..f87d02b33d0d4 100644 --- a/packages/cli/src/scaling/__tests__/scaling.service.test.ts +++ b/packages/cli/src/scaling/__tests__/scaling.service.test.ts @@ -3,7 +3,7 @@ import { ScalingService } from '../scaling.service'; import { JOB_TYPE_NAME, QUEUE_NAME } from '../constants'; import config from '@/config'; import * as BullModule from 'bull'; -import type { Job, JobData, JobOptions, JobQueue } from '../types'; +import type { Job, JobData, JobOptions, JobQueue } from '../scaling.types'; import { ApplicationError } from 'n8n-workflow'; import { mockInstance } from '@test/mocking'; import { GlobalConfig } from '@n8n/config'; @@ -22,6 +22,8 @@ jest.mock('bull', () => ({ })); describe('ScalingService', () => { + const Bull = jest.mocked(BullModule.default); + const globalConfig = mockInstance(GlobalConfig, { queue: { bull: { @@ -40,11 +42,29 @@ describe('ScalingService', () => { const instanceSettings = Container.get(InstanceSettings); const orchestrationService = mock({ isMultiMainSetupEnabled: false }); const jobProcessor = mock(); + let scalingService: ScalingService; + let registerMainListenersSpy: jest.SpyInstance; + let registerWorkerListenersSpy: jest.SpyInstance; + let scheduleQueueRecoverySpy: jest.SpyInstance; + let stopQueueRecoverySpy: jest.SpyInstance; + let getRunningJobsCountSpy: jest.SpyInstance; + + const bullConstructorArgs = [ + QUEUE_NAME, + { + prefix: globalConfig.queue.bull.prefix, + settings: globalConfig.queue.bull.settings, + createClient: expect.any(Function), + }, + ]; + beforeEach(() => { jest.clearAllMocks(); config.set('generic.instanceType', 'main'); + instanceSettings.markAsLeader(); + scalingService = new ScalingService( mock(), mock(), @@ -54,98 +74,93 @@ describe('ScalingService', () => { instanceSettings, orchestrationService, ); - }); - afterEach(() => { - scalingService.stopQueueRecovery(); + getRunningJobsCountSpy = jest.spyOn(scalingService, 'getRunningJobsCount'); + + // @ts-expect-error Private method + ScalingService.prototype.scheduleQueueRecovery = jest.fn(); + // @ts-expect-error Private method + registerMainListenersSpy = jest.spyOn(scalingService, 'registerMainListeners'); + // @ts-expect-error Private method + registerWorkerListenersSpy = jest.spyOn(scalingService, 'registerWorkerListeners'); + // @ts-expect-error Private method + scheduleQueueRecoverySpy = jest.spyOn(scalingService, 'scheduleQueueRecovery'); + // @ts-expect-error Private method + stopQueueRecoverySpy = jest.spyOn(scalingService, 'stopQueueRecovery'); }); describe('setupQueue', () => { - it('should set up the queue', async () => { - /** - * Arrange - */ - const { prefix, settings } = globalConfig.queue.bull; - const Bull = jest.mocked(BullModule.default); - - /** - * Act - */ - await scalingService.setupQueue(); + describe('if leader main', () => { + it('should set up queue + listeners + queue recovery', async () => { + await scalingService.setupQueue(); + + expect(Bull).toHaveBeenCalledWith(...bullConstructorArgs); + expect(registerMainListenersSpy).toHaveBeenCalled(); + expect(registerWorkerListenersSpy).not.toHaveBeenCalled(); + expect(scheduleQueueRecoverySpy).toHaveBeenCalled(); + }); + }); + + describe('if follower main', () => { + it('should set up queue + listeners', async () => { + instanceSettings.markAsFollower(); + + await scalingService.setupQueue(); + + expect(Bull).toHaveBeenCalledWith(...bullConstructorArgs); + expect(registerMainListenersSpy).toHaveBeenCalled(); + expect(registerWorkerListenersSpy).not.toHaveBeenCalled(); + expect(scheduleQueueRecoverySpy).not.toHaveBeenCalled(); + }); + }); + + describe('if worker', () => { + it('should set up queue + listeners', async () => { + // @ts-expect-error Private field + scalingService.instanceType = 'worker'; - /** - * Assert - */ - expect(Bull).toHaveBeenCalledWith(QUEUE_NAME, { - prefix, - settings, - createClient: expect.any(Function), + await scalingService.setupQueue(); + + expect(Bull).toHaveBeenCalledWith(...bullConstructorArgs); + expect(registerWorkerListenersSpy).toHaveBeenCalled(); + expect(registerMainListenersSpy).not.toHaveBeenCalled(); }); - expect(queue.on).toHaveBeenCalledWith('global:progress', expect.any(Function)); - expect(queue.on).toHaveBeenCalledWith('error', expect.any(Function)); }); }); describe('setupWorker', () => { it('should set up a worker with concurrency', async () => { - /** - * Arrange - */ - config.set('generic.instanceType', 'worker'); - const scalingService = new ScalingService( - mock(), - mock(), - mock(), - globalConfig, - mock(), - instanceSettings, - orchestrationService, - ); + // @ts-expect-error Private field + scalingService.instanceType = 'worker'; await scalingService.setupQueue(); const concurrency = 5; - /** - * Act - */ scalingService.setupWorker(concurrency); - /** - * Assert - */ expect(queue.process).toHaveBeenCalledWith(JOB_TYPE_NAME, concurrency, expect.any(Function)); }); it('should throw if called on a non-worker instance', async () => { - /** - * Arrange - */ await scalingService.setupQueue(); - /** - * Act and Assert - */ + expect(() => scalingService.setupWorker(5)).toThrow(); + }); + + it('should throw if called before queue is ready', async () => { + // @ts-expect-error Private field + scalingService.instanceType = 'worker'; + expect(() => scalingService.setupWorker(5)).toThrow(); }); }); describe('stop', () => { - it('should pause the queue, check for running jobs, and stop queue recovery', async () => { - /** - * Arrange - */ + it('should pause queue, wait for running jobs, stop queue recovery', async () => { await scalingService.setupQueue(); jobProcessor.getRunningJobIds.mockReturnValue([]); - const stopQueueRecoverySpy = jest.spyOn(scalingService, 'stopQueueRecovery'); - const getRunningJobsCountSpy = jest.spyOn(scalingService, 'getRunningJobsCount'); - /** - * Act - */ await scalingService.stop(); - /** - * Assert - */ expect(queue.pause).toHaveBeenCalledWith(true, true); expect(stopQueueRecoverySpy).toHaveBeenCalled(); expect(getRunningJobsCountSpy).toHaveBeenCalled(); @@ -154,62 +169,35 @@ describe('ScalingService', () => { describe('pingQueue', () => { it('should ping the queue', async () => { - /** - * Arrange - */ await scalingService.setupQueue(); - /** - * Act - */ await scalingService.pingQueue(); - /** - * Assert - */ expect(queue.client.ping).toHaveBeenCalled(); }); }); describe('addJob', () => { it('should add a job', async () => { - /** - * Arrange - */ await scalingService.setupQueue(); queue.add.mockResolvedValue(mock({ id: '456' })); - /** - * Act - */ const jobData = mock({ executionId: '123' }); const jobOptions = mock(); await scalingService.addJob(jobData, jobOptions); - /** - * Assert - */ expect(queue.add).toHaveBeenCalledWith(JOB_TYPE_NAME, jobData, jobOptions); }); }); describe('getJob', () => { it('should get a job', async () => { - /** - * Arrange - */ await scalingService.setupQueue(); const jobId = '123'; queue.getJob.mockResolvedValue(mock({ id: jobId })); - /** - * Act - */ const job = await scalingService.getJob(jobId); - /** - * Assert - */ expect(queue.getJob).toHaveBeenCalledWith(jobId); expect(job?.id).toBe(jobId); }); @@ -217,88 +205,49 @@ describe('ScalingService', () => { describe('findJobsByStatus', () => { it('should find jobs by status', async () => { - /** - * Arrange - */ await scalingService.setupQueue(); queue.getJobs.mockResolvedValue([mock({ id: '123' })]); - /** - * Act - */ const jobs = await scalingService.findJobsByStatus(['active']); - /** - * Assert - */ expect(queue.getJobs).toHaveBeenCalledWith(['active']); expect(jobs).toHaveLength(1); expect(jobs.at(0)?.id).toBe('123'); }); it('should filter out `null` in Redis response', async () => { - /** - * Arrange - */ await scalingService.setupQueue(); // @ts-expect-error - Untyped but possible Redis response queue.getJobs.mockResolvedValue([mock(), null]); - /** - * Act - */ const jobs = await scalingService.findJobsByStatus(['waiting']); - /** - * Assert - */ expect(jobs).toHaveLength(1); }); }); describe('stopJob', () => { it('should stop an active job', async () => { - /** - * Arrange - */ await scalingService.setupQueue(); const job = mock({ isActive: jest.fn().mockResolvedValue(true) }); - /** - * Act - */ const result = await scalingService.stopJob(job); - /** - * Assert - */ expect(job.progress).toHaveBeenCalledWith({ kind: 'abort-job' }); expect(result).toBe(true); }); it('should stop an inactive job', async () => { - /** - * Arrange - */ await scalingService.setupQueue(); const job = mock({ isActive: jest.fn().mockResolvedValue(false) }); - /** - * Act - */ const result = await scalingService.stopJob(job); - /** - * Assert - */ expect(job.remove).toHaveBeenCalled(); expect(result).toBe(true); }); it('should report failure to stop a job', async () => { - /** - * Arrange - */ await scalingService.setupQueue(); const job = mock({ isActive: jest.fn().mockImplementation(() => { @@ -306,53 +255,9 @@ describe('ScalingService', () => { }), }); - /** - * Act - */ const result = await scalingService.stopJob(job); - /** - * Assert - */ expect(result).toBe(false); }); }); - - describe('scheduleQueueRecovery', () => { - it('if leader, should schedule queue recovery', async () => { - /** - * Arrange - */ - const scheduleSpy = jest.spyOn(scalingService, 'scheduleQueueRecovery'); - instanceSettings.markAsLeader(); - - /** - * Act - */ - await scalingService.setupQueue(); - - /** - * Assert - */ - expect(scheduleSpy).toHaveBeenCalled(); - }); - - it('if follower, should not schedule queue recovery', async () => { - /** - * Arrange - */ - const scheduleSpy = jest.spyOn(scalingService, 'scheduleQueueRecovery'); - instanceSettings.markAsFollower(); - - /** - * Act - */ - await scalingService.setupQueue(); - - /** - * Assert - */ - expect(scheduleSpy).not.toHaveBeenCalled(); - }); - }); }); diff --git a/packages/cli/src/scaling/job-processor.ts b/packages/cli/src/scaling/job-processor.ts index 8618424e3562c..7804a6d6c85fe 100644 --- a/packages/cli/src/scaling/job-processor.ts +++ b/packages/cli/src/scaling/job-processor.ts @@ -8,7 +8,7 @@ import { WorkflowRepository } from '@/databases/repositories/workflow.repository import * as WorkflowExecuteAdditionalData from '@/workflow-execute-additional-data'; import { NodeTypes } from '@/node-types'; import type { ExecutionStatus, IExecuteResponsePromiseData, IRun } from 'n8n-workflow'; -import type { Job, JobId, JobResult, RunningJob, RunningJobSummary } from './types'; +import type { Job, JobId, JobResult, RunningJob, RunningJobSummary } from './scaling.types'; import type PCancelable from 'p-cancelable'; /** diff --git a/packages/cli/src/scaling/scaling.service.ts b/packages/cli/src/scaling/scaling.service.ts index 4dbdce1aac837..37436cdb4361e 100644 --- a/packages/cli/src/scaling/scaling.service.ts +++ b/packages/cli/src/scaling/scaling.service.ts @@ -13,11 +13,11 @@ import type { Job, JobData, JobOptions, - JobMessage, JobStatus, JobId, QueueRecoveryContext, -} from './types'; + PubSubMessage, +} from './scaling.types'; import type { IExecuteResponsePromiseData } from 'n8n-workflow'; import { GlobalConfig } from '@n8n/config'; import { ExecutionRepository } from '@/databases/repositories/execution.repository'; @@ -71,6 +71,7 @@ export class ScalingService { setupWorker(concurrency: number) { this.assertWorker(); + this.assertQueue(); void this.queue.process( JOB_TYPE_NAME, @@ -161,22 +162,6 @@ export class ScalingService { // #region Listeners private registerListeners() { - this.queue.on('global:progress', (_jobId: JobId, msg: JobMessage) => { - if (msg.kind === 'respond-to-webhook') { - const { executionId, response } = msg; - this.activeExecutions.resolveResponsePromise( - executionId, - this.decodeWebhookResponse(response), - ); - } - }); - - this.queue.on('global:progress', (jobId: JobId, msg: JobMessage) => { - if (msg.kind === 'abort-job') { - this.jobProcessor.stopJob(jobId); - } - }); - let latestAttemptTs = 0; let cumulativeTimeoutMs = 0; @@ -210,10 +195,28 @@ export class ScalingService { return; } - if ( - this.instanceType === 'worker' && - error.message.includes('job stalled more than maxStalledCount') - ) { + throw error; + }); + + if (this.instanceType === 'main') { + this.registerMainListeners(); + } else if (this.instanceType === 'worker') { + this.registerWorkerListeners(); + } + } + + /** + * Register listeners on a `worker` process for Bull queue events. + */ + private registerWorkerListeners() { + this.queue.on('global:progress', (jobId: JobId, msg: unknown) => { + if (!this.isPubSubMessage(msg)) return; + + if (msg.kind === 'abort-job') this.jobProcessor.stopJob(jobId); + }); + + this.queue.on('error', (error: Error) => { + if (error.message.includes('job stalled more than maxStalledCount')) { throw new MaxStalledCountError(error); } @@ -221,10 +224,7 @@ export class ScalingService { * Non-recoverable error on worker start with Redis unavailable. * Even if Redis recovers, worker will remain unable to process jobs. */ - if ( - this.instanceType === 'worker' && - error.message.includes('Error initializing Lua scripts') - ) { + if (error.message.includes('Error initializing Lua scripts')) { this.logger.error('[ScalingService] Fatal error initializing worker', { error }); this.logger.error('[ScalingService] Exiting process...'); process.exit(1); @@ -234,6 +234,24 @@ export class ScalingService { }); } + /** + * Register listeners on a `main` process for Bull queue events. + */ + private registerMainListeners() { + this.queue.on('global:progress', (_jobId: JobId, msg: unknown) => { + if (!this.isPubSubMessage(msg)) return; + + if (msg.kind === 'respond-to-webhook') { + const decodedResponse = this.decodeWebhookResponse(msg.response); + this.activeExecutions.resolveResponsePromise(msg.executionId, decodedResponse); + } + }); + } + + private isPubSubMessage(candidate: unknown): candidate is PubSubMessage { + return typeof candidate === 'object' && candidate !== null && 'kind' in candidate; + } + // #endregion private decodeWebhookResponse( @@ -252,6 +270,12 @@ export class ScalingService { return response; } + private assertQueue() { + if (this.queue) return; + + throw new ApplicationError('This method must be called after `setupQueue`'); + } + private assertWorker() { if (this.instanceType === 'worker') return; @@ -265,7 +289,7 @@ export class ScalingService { waitMs: config.getEnv('executions.queueRecovery.interval') * 60 * 1000, }; - scheduleQueueRecovery(waitMs = this.queueRecoveryContext.waitMs) { + private scheduleQueueRecovery(waitMs = this.queueRecoveryContext.waitMs) { this.queueRecoveryContext.timeout = setTimeout(async () => { try { const nextWaitMs = await this.recoverFromQueue(); @@ -285,7 +309,7 @@ export class ScalingService { this.logger.debug(`[ScalingService] Scheduled queue recovery check for next ${wait}`); } - stopQueueRecovery() { + private stopQueueRecovery() { clearTimeout(this.queueRecoveryContext.timeout); } diff --git a/packages/cli/src/scaling/types.ts b/packages/cli/src/scaling/scaling.types.ts similarity index 85% rename from packages/cli/src/scaling/types.ts rename to packages/cli/src/scaling/scaling.types.ts index b35d1d109d7c4..2599cf594bd1e 100644 --- a/packages/cli/src/scaling/types.ts +++ b/packages/cli/src/scaling/scaling.types.ts @@ -28,16 +28,19 @@ export type JobStatus = Bull.JobStatus; export type JobOptions = Bull.JobOptions; -/** Message sent by worker to queue or by queue to worker. */ -export type JobMessage = RepondToWebhookMessage | AbortJobMessage; +export type PubSubMessage = MessageToMain | MessageToWorker; -export type RepondToWebhookMessage = { +type MessageToMain = RepondToWebhookMessage; + +type MessageToWorker = AbortJobMessage; + +type RepondToWebhookMessage = { kind: 'respond-to-webhook'; executionId: string; response: IExecuteResponsePromiseData; }; -export type AbortJobMessage = { +type AbortJobMessage = { kind: 'abort-job'; }; diff --git a/packages/cli/src/server.ts b/packages/cli/src/server.ts index 30de80295ab6b..04faa431fe185 100644 --- a/packages/cli/src/server.ts +++ b/packages/cli/src/server.ts @@ -31,7 +31,7 @@ import { setupPushServer, setupPushHandler } from '@/push'; import { isLdapEnabled } from '@/ldap/helpers.ee'; import { AbstractServer } from '@/abstract-server'; import { PostHogClient } from '@/posthog'; -import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus'; +import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus'; import { handleMfaDisable, isMfaFeatureEnabled } from '@/mfa/helpers'; import type { FrontendService } from '@/services/frontend.service'; import { OrchestrationService } from '@/services/orchestration.service'; @@ -155,10 +155,10 @@ export class Server extends AbstractServer { // ---------------------------------------- try { const { SourceControlService } = await import( - '@/environments/sourceControl/sourceControl.service.ee' + '@/environments/source-control/source-control.service.ee' ); await Container.get(SourceControlService).init(); - await import('@/environments/sourceControl/sourceControl.controller.ee'); + await import('@/environments/source-control/source-control.controller.ee'); await import('@/environments/variables/variables.controller.ee'); } catch (error) { this.logger.warn(`Source Control initialization failed: ${(error as Error).message}`); diff --git a/packages/cli/src/services/__tests__/orchestration.service.test.ts b/packages/cli/src/services/__tests__/orchestration.service.test.ts index 99c92ad0700e8..811643b9671ea 100644 --- a/packages/cli/src/services/__tests__/orchestration.service.test.ts +++ b/packages/cli/src/services/__tests__/orchestration.service.test.ts @@ -7,7 +7,7 @@ import type { WorkflowActivateMode } from 'n8n-workflow'; import config from '@/config'; import { OrchestrationService } from '@/services/orchestration.service'; import type { RedisServiceWorkerResponseObject } from '@/services/redis/RedisServiceCommands'; -import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus'; +import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus'; import { RedisService } from '@/services/redis.service'; import { handleWorkerResponseMessageMain } from '@/services/orchestration/main/handleWorkerResponseMessageMain'; import { handleCommandMessageMain } from '@/services/orchestration/main/handleCommandMessageMain'; diff --git a/packages/cli/src/services/frontend.service.ts b/packages/cli/src/services/frontend.service.ts index 24f8a3505b0de..1e70c972a05ab 100644 --- a/packages/cli/src/services/frontend.service.ts +++ b/packages/cli/src/services/frontend.service.ts @@ -22,7 +22,7 @@ import { License } from '@/license'; import { getCurrentAuthenticationMethod } from '@/sso/sso-helpers'; import { getLdapLoginLabel } from '@/ldap/helpers.ee'; import { getSamlLoginLabel } from '@/sso/saml/saml-helpers'; -import { getVariablesLimit } from '@/environments/variables/environmentHelpers'; +import { getVariablesLimit } from '@/environments/variables/environment-helpers'; import { getWorkflowHistoryLicensePruneTime, getWorkflowHistoryPruneTime, diff --git a/packages/cli/src/services/orchestration/main/handleCommandMessageMain.ts b/packages/cli/src/services/orchestration/main/handleCommandMessageMain.ts index 1cdb3cf7f1cef..266304ff30929 100644 --- a/packages/cli/src/services/orchestration/main/handleCommandMessageMain.ts +++ b/packages/cli/src/services/orchestration/main/handleCommandMessageMain.ts @@ -1,7 +1,7 @@ import { Container } from 'typedi'; import { debounceMessageReceiver, messageToRedisServiceCommandObject } from '../helpers'; import config from '@/config'; -import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus'; +import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus'; import { ExternalSecretsManager } from '@/external-secrets/external-secrets-manager.ee'; import { License } from '@/license'; import { Logger } from '@/logger'; diff --git a/packages/cli/src/services/orchestration/webhook/handleCommandMessageWebhook.ts b/packages/cli/src/services/orchestration/webhook/handleCommandMessageWebhook.ts index c6f746db639b1..7f01017e72b94 100644 --- a/packages/cli/src/services/orchestration/webhook/handleCommandMessageWebhook.ts +++ b/packages/cli/src/services/orchestration/webhook/handleCommandMessageWebhook.ts @@ -1,6 +1,6 @@ import { ExternalSecretsManager } from '@/external-secrets/external-secrets-manager.ee'; import { License } from '@/license'; -import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus'; +import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus'; import Container from 'typedi'; import { Logger } from 'winston'; import { messageToRedisServiceCommandObject, debounceMessageReceiver } from '../helpers'; diff --git a/packages/cli/src/services/orchestration/worker/handleCommandMessageWorker.ts b/packages/cli/src/services/orchestration/worker/handleCommandMessageWorker.ts index 52727c37e199e..f3a991fb1681d 100644 --- a/packages/cli/src/services/orchestration/worker/handleCommandMessageWorker.ts +++ b/packages/cli/src/services/orchestration/worker/handleCommandMessageWorker.ts @@ -4,7 +4,7 @@ import type { RedisServiceCommandObject } from '@/services/redis/RedisServiceCom import { COMMAND_REDIS_CHANNEL } from '@/services/redis/RedisConstants'; import * as os from 'os'; import { License } from '@/license'; -import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus'; +import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus'; import { ExternalSecretsManager } from '@/external-secrets/external-secrets-manager.ee'; import { debounceMessageReceiver, getOsCpuString } from '../helpers'; import type { WorkerCommandReceivedHandlerOptions } from './types'; diff --git a/packages/cli/src/services/orchestration/worker/types.ts b/packages/cli/src/services/orchestration/worker/types.ts index 84c515466e2c2..957a6106badf8 100644 --- a/packages/cli/src/services/orchestration/worker/types.ts +++ b/packages/cli/src/services/orchestration/worker/types.ts @@ -1,6 +1,6 @@ import type { ExecutionStatus, WorkflowExecuteMode } from 'n8n-workflow'; import type { RedisServicePubSubPublisher } from '../../redis/RedisServicePubSubPublisher'; -import type { RunningJobSummary } from '@/scaling/types'; +import type { RunningJobSummary } from '@/scaling/scaling.types'; export interface WorkerCommandReceivedHandlerOptions { queueModeId: string; diff --git a/packages/cli/src/services/redis/redis-client.service.ts b/packages/cli/src/services/redis/redis-client.service.ts index bf70d8114d37e..822392708631b 100644 --- a/packages/cli/src/services/redis/redis-client.service.ts +++ b/packages/cli/src/services/redis/redis-client.service.ts @@ -2,12 +2,10 @@ import { Service } from 'typedi'; import { Logger } from '@/logger'; import ioRedis from 'ioredis'; import type { Cluster, RedisOptions } from 'ioredis'; -import type { RedisClientType } from './redis.types'; - -import { OnShutdown } from '@/decorators/on-shutdown'; -import { LOWEST_SHUTDOWN_PRIORITY } from '@/constants'; import { GlobalConfig } from '@n8n/config'; +import type { RedisClientType } from './redis.types'; + @Service() export class RedisClientService { private readonly clients = new Set(); @@ -28,13 +26,6 @@ export class RedisClientService { return client; } - @OnShutdown(LOWEST_SHUTDOWN_PRIORITY) - disconnectClients() { - for (const client of this.clients) { - client.disconnect(); - } - } - /** * Ensure prefix is wrapped in curly braces for Redis cluster. * See: https://github.com/OptimalBits/bull/blob/develop/PATTERNS.md diff --git a/packages/cli/src/telemetry/index.ts b/packages/cli/src/telemetry/index.ts index 2aec0f07d2cfb..15ed348fa9fbd 100644 --- a/packages/cli/src/telemetry/index.ts +++ b/packages/cli/src/telemetry/index.ts @@ -11,7 +11,7 @@ import { Logger } from '@/logger'; import { License } from '@/license'; import { LOWEST_SHUTDOWN_PRIORITY, N8N_VERSION } from '@/constants'; import { WorkflowRepository } from '@db/repositories/workflow.repository'; -import { SourceControlPreferencesService } from '../environments/sourceControl/sourceControlPreferences.service.ee'; +import { SourceControlPreferencesService } from '../environments/source-control/source-control-preferences.service.ee'; import { UserRepository } from '@db/repositories/user.repository'; import { ProjectRepository } from '@/databases/repositories/project.repository'; import { ProjectRelationRepository } from '@/databases/repositories/projectRelation.repository'; diff --git a/packages/cli/src/workflow-execute-additional-data.ts b/packages/cli/src/workflow-execute-additional-data.ts index 459c94692666d..2e0bda061eda9 100644 --- a/packages/cli/src/workflow-execute-additional-data.ts +++ b/packages/cli/src/workflow-execute-additional-data.ts @@ -69,7 +69,7 @@ import { WorkflowStaticDataService } from './workflows/workflow-static-data.serv import { WorkflowRepository } from './databases/repositories/workflow.repository'; import { UrlService } from './services/url.service'; import { WorkflowExecutionService } from './workflows/workflow-execution.service'; -import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus'; +import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus'; import { EventService } from './events/event.service'; import { GlobalConfig } from '@n8n/config'; import { SubworkflowPolicyChecker } from './subworkflows/subworkflow-policy-checker.service'; diff --git a/packages/cli/src/workflow-runner.ts b/packages/cli/src/workflow-runner.ts index b8012b3d4f25a..acfbdafd0135e 100644 --- a/packages/cli/src/workflow-runner.ts +++ b/packages/cli/src/workflow-runner.ts @@ -28,7 +28,7 @@ import { ExecutionRepository } from '@db/repositories/execution.repository'; import { ExternalHooks } from '@/external-hooks'; import type { IExecutionResponse, IWorkflowExecutionDataProcess } from '@/Interfaces'; import { NodeTypes } from '@/node-types'; -import type { Job, JobData, JobResult } from '@/scaling/types'; +import type { Job, JobData, JobResult } from '@/scaling/scaling.types'; import type { ScalingService } from '@/scaling/scaling.service'; import * as WorkflowHelpers from '@/workflow-helpers'; import * as WorkflowExecuteAdditionalData from '@/workflow-execute-additional-data'; diff --git a/packages/cli/test/integration/commands/worker.cmd.test.ts b/packages/cli/test/integration/commands/worker.cmd.test.ts index aad419d23b177..3bfac6a07c669 100644 --- a/packages/cli/test/integration/commands/worker.cmd.test.ts +++ b/packages/cli/test/integration/commands/worker.cmd.test.ts @@ -3,7 +3,7 @@ import { BinaryDataService } from 'n8n-core'; import { Worker } from '@/commands/worker'; import config from '@/config'; import { ExternalSecretsManager } from '@/external-secrets/external-secrets-manager.ee'; -import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus'; +import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus'; import { LoadNodesAndCredentials } from '@/load-nodes-and-credentials'; import { OrchestrationHandlerWorkerService } from '@/services/orchestration/worker/orchestration.handler.worker.service'; import { OrchestrationWorkerService } from '@/services/orchestration/worker/orchestration.worker.service'; diff --git a/packages/cli/test/integration/environments/SourceControl.test.ts b/packages/cli/test/integration/environments/SourceControl.test.ts index cac369422b772..5bb5d8089b597 100644 --- a/packages/cli/test/integration/environments/SourceControl.test.ts +++ b/packages/cli/test/integration/environments/SourceControl.test.ts @@ -2,9 +2,9 @@ import { Container } from 'typedi'; import type { User } from '@db/entities/User'; import config from '@/config'; -import { SourceControlPreferencesService } from '@/environments/sourceControl/sourceControlPreferences.service.ee'; -import { SourceControlService } from '@/environments/sourceControl/sourceControl.service.ee'; -import type { SourceControlledFile } from '@/environments/sourceControl/types/sourceControlledFile'; +import { SourceControlPreferencesService } from '@/environments/source-control/source-control-preferences.service.ee'; +import { SourceControlService } from '@/environments/source-control/source-control.service.ee'; +import type { SourceControlledFile } from '@/environments/source-control/types/source-controlled-file'; import * as utils from '../shared/utils/'; import { createUser } from '../shared/db/users'; diff --git a/packages/cli/test/integration/environments/source-control-import.service.test.ts b/packages/cli/test/integration/environments/source-control-import.service.test.ts index 4665178a3fb11..56ba90cddc1d5 100644 --- a/packages/cli/test/integration/environments/source-control-import.service.test.ts +++ b/packages/cli/test/integration/environments/source-control-import.service.test.ts @@ -7,12 +7,12 @@ import { nanoid } from 'nanoid'; import type { InstanceSettings } from 'n8n-core'; import * as testDb from '../shared/testDb'; -import { SourceControlImportService } from '@/environments/sourceControl/sourceControlImport.service.ee'; +import { SourceControlImportService } from '@/environments/source-control/source-control-import.service.ee'; import { createMember, getGlobalOwner } from '../shared/db/users'; import { SharedCredentialsRepository } from '@/databases/repositories/sharedCredentials.repository'; import { mockInstance } from '../../shared/mocking'; -import type { SourceControlledFile } from '@/environments/sourceControl/types/sourceControlledFile'; -import type { ExportableCredential } from '@/environments/sourceControl/types/exportableCredential'; +import type { SourceControlledFile } from '@/environments/source-control/types/source-controlled-file'; +import type { ExportableCredential } from '@/environments/source-control/types/exportable-credential'; import { createTeamProject, getPersonalProject } from '../shared/db/projects'; import { ProjectRepository } from '@/databases/repositories/project.repository'; import { saveCredential } from '../shared/db/credentials'; diff --git a/packages/cli/test/integration/eventbus.ee.test.ts b/packages/cli/test/integration/eventbus.ee.test.ts index f21822683b854..9146b8b87c392 100644 --- a/packages/cli/test/integration/eventbus.ee.test.ts +++ b/packages/cli/test/integration/eventbus.ee.test.ts @@ -14,13 +14,13 @@ import { } from 'n8n-workflow'; import type { User } from '@db/entities/User'; -import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus'; -import { EventMessageGeneric } from '@/eventbus/EventMessageClasses/EventMessageGeneric'; -import type { MessageEventBusDestinationSyslog } from '@/eventbus/MessageEventBusDestination/MessageEventBusDestinationSyslog.ee'; -import type { MessageEventBusDestinationWebhook } from '@/eventbus/MessageEventBusDestination/MessageEventBusDestinationWebhook.ee'; -import type { MessageEventBusDestinationSentry } from '@/eventbus/MessageEventBusDestination/MessageEventBusDestinationSentry.ee'; -import { EventMessageAudit } from '@/eventbus/EventMessageClasses/EventMessageAudit'; -import type { EventNamesTypes } from '@/eventbus/EventMessageClasses'; +import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus'; +import { EventMessageGeneric } from '@/eventbus/event-message-classes/event-message-generic'; +import type { MessageEventBusDestinationSyslog } from '@/eventbus/message-event-bus-destination/message-event-bus-destination-syslog.ee'; +import type { MessageEventBusDestinationWebhook } from '@/eventbus/message-event-bus-destination/message-event-bus-destination-webhook.ee'; +import type { MessageEventBusDestinationSentry } from '@/eventbus/message-event-bus-destination/message-event-bus-destination-sentry.ee'; +import { EventMessageAudit } from '@/eventbus/event-message-classes/event-message-audit'; +import type { EventNamesTypes } from '@/eventbus/event-message-classes'; import { ExecutionRecoveryService } from '@/executions/execution-recovery.service'; import * as utils from './shared/utils'; @@ -28,7 +28,7 @@ import { createUser } from './shared/db/users'; import { mockInstance } from '../shared/mocking'; import type { SuperAgentTest } from './shared/types'; -jest.unmock('@/eventbus/MessageEventBus/MessageEventBus'); +jest.unmock('@/eventbus/message-event-bus/message-event-bus'); jest.mock('axios'); const mockedAxios = axios as jest.Mocked; jest.mock('syslog-client'); @@ -96,7 +96,7 @@ beforeAll(async () => { }); afterAll(async () => { - jest.mock('@/eventbus/MessageEventBus/MessageEventBus'); + jest.mock('@/eventbus/message-event-bus/message-event-bus'); await eventBus?.close(); }); diff --git a/packages/cli/test/integration/eventbus.test.ts b/packages/cli/test/integration/eventbus.test.ts index e4e58f96fa330..3b36222ec2cd1 100644 --- a/packages/cli/test/integration/eventbus.test.ts +++ b/packages/cli/test/integration/eventbus.test.ts @@ -1,5 +1,5 @@ import type { User } from '@db/entities/User'; -import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus'; +import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus'; import { ExecutionRecoveryService } from '@/executions/execution-recovery.service'; import * as utils from './shared/utils/'; diff --git a/packages/cli/test/integration/prometheus-metrics.test.ts b/packages/cli/test/integration/prometheus-metrics.test.ts index 1eccb9b7d0ce2..f6710ba3a5ec4 100644 --- a/packages/cli/test/integration/prometheus-metrics.test.ts +++ b/packages/cli/test/integration/prometheus-metrics.test.ts @@ -7,7 +7,7 @@ import { PrometheusMetricsService } from '@/metrics/prometheus-metrics.service'; import { setupTestServer } from './shared/utils'; import { GlobalConfig } from '@n8n/config'; -jest.unmock('@/eventbus/MessageEventBus/MessageEventBus'); +jest.unmock('@/eventbus/message-event-bus/message-event-bus'); const toLines = (response: Response) => response.text.trim().split('\n'); diff --git a/packages/cli/test/integration/shared/utils/testServer.ts b/packages/cli/test/integration/shared/utils/testServer.ts index c40285829e805..b75a7804b74d8 100644 --- a/packages/cli/test/integration/shared/utils/testServer.ts +++ b/packages/cli/test/integration/shared/utils/testServer.ts @@ -179,7 +179,7 @@ export const setupTestServer = ({ break; case 'sourceControl': - await import('@/environments/sourceControl/sourceControl.controller.ee'); + await import('@/environments/source-control/source-control.controller.ee'); break; case 'community-packages': diff --git a/packages/cli/test/integration/workflows/workflow.service.test.ts b/packages/cli/test/integration/workflows/workflow.service.test.ts index 1853c17839288..084b41d212214 100644 --- a/packages/cli/test/integration/workflows/workflow.service.test.ts +++ b/packages/cli/test/integration/workflows/workflow.service.test.ts @@ -3,7 +3,7 @@ import { mock } from 'jest-mock-extended'; import { ActiveWorkflowManager } from '@/active-workflow-manager'; import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository'; import { WorkflowRepository } from '@db/repositories/workflow.repository'; -import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus'; +import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus'; import { Telemetry } from '@/telemetry'; import { OrchestrationService } from '@/services/orchestration.service'; import { WorkflowService } from '@/workflows/workflow.service'; diff --git a/packages/cli/test/setup-mocks.ts b/packages/cli/test/setup-mocks.ts index c6db2d147cdc8..63d60cd5224ee 100644 --- a/packages/cli/test/setup-mocks.ts +++ b/packages/cli/test/setup-mocks.ts @@ -3,5 +3,5 @@ import 'reflect-metadata'; jest.mock('@sentry/node'); jest.mock('@n8n_io/license-sdk'); jest.mock('@/telemetry'); -jest.mock('@/eventbus/MessageEventBus/MessageEventBus'); +jest.mock('@/eventbus/message-event-bus/message-event-bus'); jest.mock('@/push'); diff --git a/packages/core/src/InstanceSettings.ts b/packages/core/src/InstanceSettings.ts index f75e1df712108..fb57cbc36b7c4 100644 --- a/packages/core/src/InstanceSettings.ts +++ b/packages/core/src/InstanceSettings.ts @@ -40,8 +40,15 @@ export class InstanceSettings { readonly instanceId = this.generateInstanceId(); - /** Always `leader` in single-main setup. `leader` or `follower` in multi-main setup. */ - private instanceRole: InstanceRole = 'unset'; + /** + * A main is: + * - `unset` during bootup, + * - `leader` after bootup in single-main setup, + * - `leader` or `follower` after bootup in multi-main setup. + * + * A non-main instance type (e.g. `worker`) is always `unset`. + */ + instanceRole: InstanceRole = 'unset'; get isLeader() { return this.instanceRole === 'leader'; diff --git a/packages/design-system/src/components/AskAssistantAvatar/__tests__/AskAssistantAvatar.spec.ts b/packages/design-system/src/components/AskAssistantAvatar/__tests__/AskAssistantAvatar.spec.ts new file mode 100644 index 0000000000000..94857ed4dbb34 --- /dev/null +++ b/packages/design-system/src/components/AskAssistantAvatar/__tests__/AskAssistantAvatar.spec.ts @@ -0,0 +1,21 @@ +import { render } from '@testing-library/vue'; +import AssistantAvatar from '../AssistantAvatar.vue'; + +describe('AskAssistantAvatar', () => { + it('renders small avatar correctly', () => { + const { container } = render(AssistantAvatar, { + props: { + size: 'small', + }, + }); + expect(container).toMatchSnapshot(); + }); + it('renders mini avatar correctly', () => { + const { container } = render(AssistantAvatar, { + props: { + size: 'mini', + }, + }); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/packages/design-system/src/components/AskAssistantAvatar/__tests__/__snapshots__/AskAssistantAvatar.spec.ts.snap b/packages/design-system/src/components/AskAssistantAvatar/__tests__/__snapshots__/AskAssistantAvatar.spec.ts.snap new file mode 100644 index 0000000000000..ba82ada82e251 --- /dev/null +++ b/packages/design-system/src/components/AskAssistantAvatar/__tests__/__snapshots__/AskAssistantAvatar.spec.ts.snap @@ -0,0 +1,87 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`AskAssistantAvatar > renders mini avatar correctly 1`] = ` +
+
+ + + + + + + + + + +
+
+`; + +exports[`AskAssistantAvatar > renders small avatar correctly 1`] = ` +
+
+ + + + + + + + + + +
+
+`; diff --git a/packages/design-system/src/components/AskAssistantButton/__tests__/AskAssistantButton.spec.ts b/packages/design-system/src/components/AskAssistantButton/__tests__/AskAssistantButton.spec.ts new file mode 100644 index 0000000000000..13d96d4a622bb --- /dev/null +++ b/packages/design-system/src/components/AskAssistantButton/__tests__/AskAssistantButton.spec.ts @@ -0,0 +1,17 @@ +import { render } from '@testing-library/vue'; +import AskAssistantButton from '../AskAssistantButton.vue'; + +describe('AskAssistantButton', () => { + it('renders default button correctly', () => { + const { container } = render(AskAssistantButton, {}); + expect(container).toMatchSnapshot(); + }); + it('renders button with unread messages correctly', () => { + const { container } = render(AskAssistantButton, { + props: { + unreadCount: 3, + }, + }); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/packages/design-system/src/components/AskAssistantButton/__tests__/__snapshots__/AskAssistantButton.spec.ts.snap b/packages/design-system/src/components/AskAssistantButton/__tests__/__snapshots__/AskAssistantButton.spec.ts.snap new file mode 100644 index 0000000000000..5ff7a87d6ec93 --- /dev/null +++ b/packages/design-system/src/components/AskAssistantButton/__tests__/__snapshots__/AskAssistantButton.spec.ts.snap @@ -0,0 +1,96 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`AskAssistantButton > renders button with unread messages correctly 1`] = ` +
+ +
+`; + +exports[`AskAssistantButton > renders default button correctly 1`] = ` +
+ +
+`; diff --git a/packages/design-system/src/components/AskAssistantChat/__tests__/AskAssistantChat.spec.ts b/packages/design-system/src/components/AskAssistantChat/__tests__/AskAssistantChat.spec.ts new file mode 100644 index 0000000000000..6634c9807dccb --- /dev/null +++ b/packages/design-system/src/components/AskAssistantChat/__tests__/AskAssistantChat.spec.ts @@ -0,0 +1,131 @@ +import { render } from '@testing-library/vue'; +import AskAssistantChat from '../AskAssistantChat.vue'; + +describe('AskAssistantChat', () => { + it('renders default placeholder chat correctly', () => { + const { container } = render(AskAssistantChat, { + props: { + user: { firstName: 'Kobi', lastName: 'Dog' }, + }, + }); + expect(container).toMatchSnapshot(); + }); + it('renders chat with messages correctly', () => { + const { container } = render(AskAssistantChat, { + props: { + user: { firstName: 'Kobi', lastName: 'Dog' }, + messages: [ + { + id: '1', + type: 'text', + role: 'assistant', + content: + 'Hi Max! Here is my top solution to fix the error in your **Transform data** node👇', + read: false, + }, + { + id: '1', + type: 'code-diff', + role: 'assistant', + description: 'Short solution description here that can spill over to two lines', + codeDiff: + '@@ -1,7 +1,6 @@\n-The Way that can be told of is not the eternal Way;\n-The name that can be named is not the eternal name.\nThe Nameless is the origin of Heaven and Earth;\n-The Named is the mother of all things.\n+The named is the mother of all things.\n+\nTherefore let there always be non-being,\nso we may see their subtlety,\nAnd let there always be being,\n@@ -9,3 +8,6 @@\n The two are the same,\n But after they are produced,\n they have different names.\n+They both may be called deep and profound.\n+Deeper and more profound,\n+The door of all subtleties!', + suggestionId: 'test', + quickReplies: [ + { + type: 'new-suggestion', + text: 'Give me another solution', + }, + { + type: 'resolved', + text: 'All good', + }, + ], + read: false, + }, + { + id: '2', + type: 'text', + role: 'user', + content: 'Give it to me **ignore this markdown**', + read: false, + }, + { + id: '2', + type: 'block', + role: 'assistant', + title: 'Credential doesn’t have correct permissions to send a message', + content: + 'Solution steps:\n1. Lorem ipsum dolor sit amet, consectetur **adipiscing** elit. Proin id nulla placerat, tristique ex at, euismod dui.\n2. Copy this into somewhere\n3. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin id nulla placerat, tristique ex at, euismod dui.\n4. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin id nulla placerat, tristique ex at, euismod dui. \n Testing more code \n - Unordered item 1 \n - Unordered item 2', + read: false, + }, + { + id: '2', + type: 'code-diff', + role: 'assistant', + description: 'Short solution with min height', + codeDiff: + '@@ -1,7 +1,6 @@\n-The Way that can be told of is not the eternal Way;\n-The name that can be named is not the eternal name.\n+The door of all subtleties!', + quickReplies: [ + { + type: 'new-suggestion', + text: 'Give me another solution', + }, + { + type: 'resolved', + text: 'All good', + }, + ], + suggestionId: 'test', + read: false, + }, + ], + }, + }); + expect(container).toMatchSnapshot(); + }); + it('renders streaming chat correctly', () => { + const { container } = render(AskAssistantChat, { + props: { + user: { firstName: 'Kobi', lastName: 'Dog' }, + messages: [ + { + id: '1', + type: 'text', + role: 'assistant', + content: + 'Hi Max! Here is my top solution to fix the error in your **Transform data** node👇', + read: false, + }, + ], + isStreaming: true, + }, + }); + expect(container).toMatchSnapshot(); + }); + it('renders end of session chat correctly', () => { + const { container } = render(AskAssistantChat, { + props: { + user: { firstName: 'Kobi', lastName: 'Dog' }, + messages: [ + { + id: '1', + type: 'text', + role: 'assistant', + content: + 'Hi Max! Here is my top solution to fix the error in your **Transform data** node👇', + read: false, + }, + { + id: '123', + role: 'assistant', + type: 'event', + eventName: 'end-session', + read: false, + }, + ], + }, + }); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/packages/design-system/src/components/AskAssistantChat/__tests__/__snapshots__/AskAssistantChat.spec.ts.snap b/packages/design-system/src/components/AskAssistantChat/__tests__/__snapshots__/AskAssistantChat.spec.ts.snap new file mode 100644 index 0000000000000..826732bdb8e59 --- /dev/null +++ b/packages/design-system/src/components/AskAssistantChat/__tests__/__snapshots__/AskAssistantChat.spec.ts.snap @@ -0,0 +1,1334 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`AskAssistantChat > renders chat with messages correctly 1`] = ` +
+
+
+
+
+ + + + + + + + + + + + AI Assistant + +
+
+ beta +
+
+
+ +
+
+
+
+ +
+
+
+ + + + + + + + + + +
+ + Assistant + +
+
+ + + + +

+ Hi Max! Here is my top solution to fix the error in your + + Transform data + + node👇 +

+ + +
+ + +
+ +
+
+ +
+
+
+ Short solution description here that can spill over to two lines +
+
+ +
+
+ + + +
+
+ +  -  + + + The Way that can be told of is not the eternal Way; + +
+
+
+
+ + + +
+
+ +  -  + + + The name that can be named is not the eternal name. + +
+
+
+
+ + + +
+
+ +  -  + + + The Named is the mother of all things. + +
+
+
+
+ + + 1 +
+
+ +  +  + + + The named is the mother of all things. + +
+
+
+
+ + + 2 +
+
+ +  +  + + +
+
+
+
+ + + 3 +
+
+ +     + + + The two are the same, + +
+
+
+
+ + + 4 +
+
+ +     + + + But after they are produced, + +
+
+
+
+ + + 5 +
+
+ +     + + + they have different names. + +
+
+
+
+ + + 6 +
+
+ +  +  + + + They both may be called deep and profound. + +
+
+
+
+ + + 7 +
+
+ +  +  + + + Deeper and more profound, + +
+
+
+
+ + + 8 +
+
+ +  +  + + + The door of all subtleties! + +
+
+ +
+
+ + Replace my code + +
+
+
+ +
+
+
+ + + You + +
+
+ + +

+ Give it to me + + ignore this markdown + +

+ + +
+ +
+ +
+
+
+
+ + + + + + + + + + +
+ + Assistant + +
+
+
+
+ Credential doesn’t have correct permissions to send a message + +
+
+ + +

+ Solution steps: +

+ + +
    + + +
  1. + Lorem ipsum dolor sit amet, consectetur + + adipiscing + + elit. Proin id nulla placerat, tristique ex at, euismod dui. +
  2. + + +
  3. + Copy this into somewhere +
  4. + + +
  5. + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin id nulla placerat, tristique ex at, euismod dui. +
  6. + + +
  7. + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin id nulla placerat, tristique ex at, euismod dui. +
    + +Testing more code +
  8. + + +
+ + +
    + + +
  • + Unordered item 1 +
  • + + +
  • + Unordered item 2 +
  • + + +
+ + +
+ +
+
+
+ +
+
+ +
+
+
+ Short solution with min height +
+
+ +
+
+ + + +
+
+ +  -  + + + The Way that can be told of is not the eternal Way; + +
+
+
+
+ + + +
+
+ +  -  + + + The name that can be named is not the eternal name. + +
+
+
+
+ + + 1 +
+
+ +  +  + + + The door of all subtleties! + +
+
+
+
+ + + +
+
+ +     + + +
+
+ +
+
+ + Replace my code + +
+
+
+
+
+ Quick reply 👇 +
+ +
+ + Give me another solution + +
+
+ + All good + +
+ +
+
+ +
+
+
+