Skip to content

Commit

Permalink
Merge branch 'cat-519-cleanup-old-canvas-code' of github.com:n8n-io/n…
Browse files Browse the repository at this point in the history
…8n into cat-519-cleanup-old-canvas-code
  • Loading branch information
alexgrozav committed Feb 19, 2025
2 parents de7ce05 + 32c0e2f commit 9d35e75
Show file tree
Hide file tree
Showing 36 changed files with 631 additions and 88 deletions.
22 changes: 21 additions & 1 deletion .github/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
services:
mariadb:
image: mariadb:10.9
image: mariadb:10.5
environment:
- MARIADB_DATABASE=n8n
- MARIADB_ROOT_PASSWORD=password
Expand All @@ -10,6 +10,26 @@ services:
tmpfs:
- /var/lib/mysql

mysql-8.0.13:
image: mysql:8.0.13
environment:
- MYSQL_DATABASE=n8n
- MYSQL_ROOT_PASSWORD=password
ports:
- 3306:3306
tmpfs:
- /var/lib/mysql

mysql-8.4:
image: mysql:8.4
environment:
- MYSQL_DATABASE=n8n
- MYSQL_ROOT_PASSWORD=password
ports:
- 3306:3306
tmpfs:
- /var/lib/mysql

postgres:
image: postgres:16
restart: always
Expand Down
48 changes: 46 additions & 2 deletions .github/workflows/ci-postgres-mysql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,50 @@ jobs:
working-directory: packages/cli
run: pnpm test:mariadb --testTimeout 30000

mysql:
name: MySQL (${{ matrix.service-name }})
runs-on: ubuntu-latest
needs: build
timeout-minutes: 20
strategy:
matrix:
service-name: [ 'mysql-8.0.13', 'mysql-8.4' ]
env:
DB_MYSQLDB_PASSWORD: password
steps:
- uses: actions/[email protected]

- uses: actions/[email protected]
with:
node-version: 20.x

- name: Setup corepack and pnpm
run: |
npm i -g [email protected]
corepack enable
- run: pnpm install --frozen-lockfile

- name: Setup build cache
uses: rharkor/[email protected]

- name: Restore cached build artifacts
uses: actions/cache/[email protected]
with:
path: ./packages/**/dist
key: ${{ github.sha }}:db-tests

- name: Start MySQL
uses: isbang/[email protected]
with:
compose-file: ./.github/docker-compose.yml
services: |
${{ matrix.service-name }}
- name: Test MySQL
working-directory: packages/cli
run: pnpm test:mysql --testTimeout 30000

postgres:
name: Postgres
runs-on: ubuntu-latest
Expand Down Expand Up @@ -168,7 +212,7 @@ jobs:
notify-on-failure:
name: Notify Slack on failure
runs-on: ubuntu-latest
needs: [mariadb, postgres]
needs: [mariadb, postgres, mysql]
steps:
- name: Notify Slack on failure
uses: act10ns/[email protected]
Expand All @@ -177,4 +221,4 @@ jobs:
status: ${{ job.status }}
channel: '#alerts-build'
webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
message: Postgres or MariaDB tests failed (${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
message: Postgres, MariaDB or MySQL tests failed (${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { GenerateCredentialNameRequestQuery } from '../generate-credential-name.dto';

describe('GenerateCredentialNameRequestQuery', () => {
describe('should pass validation', () => {
it('with empty object', () => {
const data = {};

const result = GenerateCredentialNameRequestQuery.safeParse(data);

expect(result.success).toBe(true);
expect(result.data?.name).toBeUndefined();
});

it('with valid name', () => {
const data = { name: 'My Credential' };

const result = GenerateCredentialNameRequestQuery.safeParse(data);

expect(result.success).toBe(true);
expect(result.data?.name).toBe('My Credential');
});
});

describe('should fail validation', () => {
test.each([
{ field: 'name', value: 123 },
{ field: 'name', value: true },
{ field: 'name', value: {} },
{ field: 'name', value: [] },
])('with invalid value $value for $field', ({ field, value }) => {
const data = { [field]: value };

const result = GenerateCredentialNameRequestQuery.safeParse(data);

expect(result.success).toBe(false);
expect(result.error?.issues[0].path[0]).toBe(field);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { z } from 'zod';
import { Z } from 'zod-class';

export class GenerateCredentialNameRequestQuery extends Z.class({
name: z.string().optional(),
}) {}
1 change: 1 addition & 0 deletions packages/@n8n/api-types/src/dto/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export { PushWorkFolderRequestDto } from './source-control/push-work-folder-requ
export { VariableListRequestDto } from './variables/variables-list-request.dto';
export { CredentialsGetOneRequestQuery } from './credentials/credentials-get-one-request.dto';
export { CredentialsGetManyRequestQuery } from './credentials/credentials-get-many-request.dto';
export { GenerateCredentialNameRequestQuery } from './credentials/generate-credential-name.dto';

export { ImportWorkflowFromUrlDto } from './workflows/import-workflow-from-url.dto';
export { ManualRunQueryDto } from './workflows/manual-run-query.dto';
Expand Down
2 changes: 0 additions & 2 deletions packages/@n8n/api-types/src/frontend-settings.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { FrontendBetaFeatures } from '@n8n/config';
import type { ExpressionEvaluatorType, LogLevel, WorkflowSettings } from 'n8n-workflow';

export interface IVersionNotificationSettings {
Expand Down Expand Up @@ -176,7 +175,6 @@ export interface FrontendSettings {
security: {
blockFileAccessToN8nFiles: boolean;
};
betaFeatures: FrontendBetaFeatures[];
easyAIWorkflowOnboarded: boolean;
partialExecution: {
version: 1 | 2;
Expand Down
11 changes: 0 additions & 11 deletions packages/@n8n/config/src/configs/frontend.config.ts

This file was deleted.

1 change: 0 additions & 1 deletion packages/@n8n/config/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ export { Config, Env, Nested } from './decorators';
export { TaskRunnersConfig } from './configs/runners.config';
export { SecurityConfig } from './configs/security.config';
export { ExecutionsConfig } from './configs/executions.config';
export { FrontendBetaFeatures, FrontendConfig } from './configs/frontend.config';
export { S3Config } from './configs/external-storage.config';
export { LOG_SCOPES } from './configs/logging.config';
export type { LogScope } from './configs/logging.config';
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"test:sqlite": "N8N_LOG_LEVEL=silent DB_TYPE=sqlite jest",
"test:postgres": "N8N_LOG_LEVEL=silent DB_TYPE=postgresdb DB_POSTGRESDB_SCHEMA=alt_schema DB_TABLE_PREFIX=test_ jest --no-coverage",
"test:mariadb": "N8N_LOG_LEVEL=silent DB_TYPE=mariadb DB_TABLE_PREFIX=test_ jest --no-coverage",
"test:mysql": "N8N_LOG_LEVEL=silent DB_TYPE=mysqldb DB_TABLE_PREFIX=test_ jest --no-coverage",
"watch": "tsc-watch -p tsconfig.build.json --onCompilationComplete \"tsc-alias -p tsconfig.build.json\""
},
"bin": {
Expand Down Expand Up @@ -96,7 +97,7 @@
"@n8n/task-runner": "workspace:*",
"@n8n/typeorm": "0.3.20-12",
"@n8n_io/ai-assistant-sdk": "1.13.0",
"@n8n_io/license-sdk": "2.15.1",
"@n8n_io/license-sdk": "2.16.0",
"@oclif/core": "4.0.7",
"@rudderstack/rudder-sdk-node": "2.0.9",
"@sentry/node": "catalog:",
Expand Down
14 changes: 11 additions & 3 deletions packages/cli/src/credentials/credentials.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { CredentialsGetManyRequestQuery, CredentialsGetOneRequestQuery } from '@n8n/api-types';
import {
CredentialsGetManyRequestQuery,
CredentialsGetOneRequestQuery,
GenerateCredentialNameRequestQuery,
} from '@n8n/api-types';
import { GlobalConfig } from '@n8n/config';
// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import
import { In } from '@n8n/typeorm';
Expand Down Expand Up @@ -79,8 +83,12 @@ export class CredentialsController {
}

@Get('/new')
async generateUniqueName(req: CredentialRequest.NewName) {
const requestedName = req.query.name ?? this.globalConfig.credentials.defaultName;
async generateUniqueName(
_req: unknown,
_res: unknown,
@Query query: GenerateCredentialNameRequestQuery,
) {
const requestedName = query.name ?? this.globalConfig.credentials.defaultName;

return {
name: await this.namingService.getUniqueCredentialName(requestedName),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,19 @@ export class MigrateTestDefinitionKeyToString1731582748663 implements Irreversib
await queryRunner.query(
`CREATE INDEX \`TMP_idx_${tablePrefix}test_definition_id\` ON ${tablePrefix}test_definition (\`id\`);`,
);

// Note: this part was missing in initial release and was added after. Without it the migration run successfully,
// but left the table in inconsistent state, because it didn't finish changing the primary key and deleting the old one.
// This prevented the next migration from running on MySQL 8.4.4
await queryRunner.query(
`ALTER TABLE ${tablePrefix}test_definition MODIFY COLUMN tmp_id INT NOT NULL;`,
);
await queryRunner.query(
`ALTER TABLE ${tablePrefix}test_definition DROP PRIMARY KEY, ADD PRIMARY KEY (\`id\`);`,
);
await queryRunner.query(
`DROP INDEX \`TMP_idx_${tablePrefix}test_definition_id\` ON ${tablePrefix}test_definition;`,
);
await queryRunner.query(`ALTER TABLE ${tablePrefix}test_definition DROP COLUMN tmp_id;`);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import assert from 'node:assert';

import type { MigrationContext, ReversibleMigration } from '@/databases/types';

const testMetricEntityTableName = 'test_metric';

export class CreateTestMetricTable1732271325258 implements ReversibleMigration {
async up({ schemaBuilder: { createTable, column }, queryRunner, tablePrefix }: MigrationContext) {
// Check if the previous migration MigrateTestDefinitionKeyToString1731582748663 properly updated the primary key
const table = await queryRunner.getTable(`${tablePrefix}test_definition`);
assert(table, 'test_definition table not found');

const brokenPrimaryColumn = table.primaryColumns.some(
(c) => c.name === 'tmp_id' && c.isPrimary,
);

if (brokenPrimaryColumn) {
// The migration was completed, but left the table in inconsistent state, let's finish the primary key change
await queryRunner.query(
`ALTER TABLE ${tablePrefix}test_definition MODIFY COLUMN tmp_id INT NOT NULL;`,
);
await queryRunner.query(
`ALTER TABLE ${tablePrefix}test_definition DROP PRIMARY KEY, ADD PRIMARY KEY (\`id\`);`,
);
await queryRunner.query(
`DROP INDEX \`TMP_idx_${tablePrefix}test_definition_id\` ON ${tablePrefix}test_definition;`,
);
await queryRunner.query(`ALTER TABLE ${tablePrefix}test_definition DROP COLUMN tmp_id;`);
}
// End of test_definition PK check

await createTable(testMetricEntityTableName)
.withColumns(
column('id').varchar(36).primary.notNull,
column('name').varchar(255).notNull,
column('testDefinitionId').varchar(36).notNull,
)
.withIndexOn('testDefinitionId')
.withForeignKey('testDefinitionId', {
tableName: 'test_definition',
columnName: 'id',
onDelete: 'CASCADE',
}).withTimestamps;
}

async down({ schemaBuilder: { dropTable } }: MigrationContext) {
await dropTable(testMetricEntityTableName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { MigrationContext, ReversibleMigration } from '@/databases/types';

const columns = ['totalCases', 'passedCases', 'failedCases'] as const;

// Note: This migration was separated from common after release to remove column check constraints
// because they were causing issues with MySQL

export class AddStatsColumnsToTestRun1736172058779 implements ReversibleMigration {
async up({ escape, runQuery }: MigrationContext) {
const tableName = escape.tableName('test_run');
const columnNames = columns.map((name) => escape.columnName(name));

// Values can be NULL only if the test run is new, otherwise they must be non-negative integers.
// Test run might be cancelled or interrupted by unexpected error at any moment, so values can be either NULL or non-negative integers.
for (const name of columnNames) {
await runQuery(`ALTER TABLE ${tableName} ADD COLUMN ${name} INT;`);
}
}

async down({ escape, runQuery }: MigrationContext) {
const tableName = escape.tableName('test_run');
const columnNames = columns.map((name) => escape.columnName(name));

for (const name of columnNames) {
await runQuery(`ALTER TABLE ${tableName} DROP COLUMN ${name}`);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import assert from 'node:assert';

import type { MigrationContext, IrreversibleMigration } from '@/databases/types';

export class FixTestDefinitionPrimaryKey1739873751194 implements IrreversibleMigration {
async up({ queryRunner, tablePrefix }: MigrationContext) {
/**
* MigrateTestDefinitionKeyToString migration for MySQL/MariaDB had missing part,
* and didn't complete primary key type change and deletion of the temporary column.
*
* This migration checks if table is in inconsistent state and finishes the primary key type change when needed.
*
* The MigrateTestDefinitionKeyToString migration has been patched to properly change the primary key.
*
* As the primary key issue might prevent the CreateTestMetricTable migration from running successfully on MySQL 8.4.4,
* the CreateTestMetricTable also contains the patch.
*
* For users who already ran the MigrateTestDefinitionKeyToString and CreateTestMetricTable, this migration should fix the primary key.
* For users who run these migrations in the same batch, this migration would be no-op, as the test_definition table should be already fixed
* by either of the previous patched migrations.
*/

const table = await queryRunner.getTable(`${tablePrefix}test_definition`);
assert(table, 'test_definition table not found');

const brokenPrimaryColumn = table.primaryColumns.some(
(c) => c.name === 'tmp_id' && c.isPrimary,
);

if (brokenPrimaryColumn) {
// The migration was completed, but left the table in inconsistent state, let's finish the primary key change
await queryRunner.query(
`ALTER TABLE ${tablePrefix}test_definition MODIFY COLUMN tmp_id INT NOT NULL;`,
);
await queryRunner.query(
`ALTER TABLE ${tablePrefix}test_definition DROP PRIMARY KEY, ADD PRIMARY KEY (\`id\`);`,
);
await queryRunner.query(
`DROP INDEX \`TMP_idx_${tablePrefix}test_definition_id\` ON ${tablePrefix}test_definition;`,
);
await queryRunner.query(`ALTER TABLE ${tablePrefix}test_definition DROP COLUMN tmp_id;`);
}
}
}
Loading

0 comments on commit 9d35e75

Please sign in to comment.