diff --git a/.env.local b/.env.local index e0ec146341ca8..5fdd08ef87c57 100644 --- a/.env.local +++ b/.env.local @@ -6,3 +6,6 @@ N8N_LOG_LEVEL=debug REDIS_URL='redis://localhost:6379' DB_POSTGRESDB_SCHEMA=local PORT=5678 +FOUNDELASTICSEARCH_URL='http://localhost:9200' +FOUNDELASTICSEARCH_APIKEY='' +FOUNDELASTICSEARCH_INDEX='local' diff --git a/.github/workflows/test-workflows.yml b/.github/workflows/test-workflows.yml index a40b4232dfce1..8f23d10a1b22e 100644 --- a/.github/workflows/test-workflows.yml +++ b/.github/workflows/test-workflows.yml @@ -25,7 +25,7 @@ jobs: - uses: pnpm/action-setup@v2.2.4 with: - version: 8.1.0 + version: 8.6.1 - uses: actions/setup-node@v3 with: @@ -69,8 +69,9 @@ jobs: shell: bash - name: Run tests - run: n8n/packages/cli/bin/n8n executeBatch --shallow --skipList=test-workflows/skipList.txt --shortOutput --concurrency=16 --compare=test-workflows/snapshots + run: n8n/packages/cli/bin/n8n executeBatch --shallow --skipList=test-workflows/skipList.txt --githubWorkflow --shortOutput --concurrency=16 --compare=test-workflows/snapshots shell: bash + id: tests env: N8N_ENCRYPTION_KEY: ${{secrets.ENCRYPTION_KEY}} SKIP_STATISTICS_EVENTS: true @@ -98,4 +99,7 @@ jobs: status: ${{ job.status }} channel: '#updates-build-alerts' webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} - message: Test workflows failed (${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) + message: | + 🛑 Workflow test failed 🛑: + ${{ steps.tests.outputs.slackMessage}} + Sent by *Github Action*: <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}| Test workflow> diff --git a/.gitignore b/.gitignore index dc4f2716b1986..a4ece25825b63 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,5 @@ cypress/videos/* cypress/screenshots/* *.swp .env +.env.local.with.aws dump.rdb diff --git a/CHANGELOG.md b/CHANGELOG.md index 2741287378166..96c57ce8d5cfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,27 @@ +# [0.231.0](https://github.com/n8n-io/n8n/compare/n8n@0.230.0...n8n@0.231.0) (2023-05-31) + + +### Bug Fixes + +* **Code Node:** Fix `item` and `items` alias regression ([#6331](https://github.com/n8n-io/n8n/issues/6331)) ([54e3838](https://github.com/n8n-io/n8n/commit/54e3838daed1f0931a04ba76cfd1ea7519c0e382)) +* **Code Node:** Update vm2 to address CVE-2023-32313 ([#6318](https://github.com/n8n-io/n8n/issues/6318)) ([bcbec52](https://github.com/n8n-io/n8n/commit/bcbec52552d52b0509659cab13112e1377a256b3)) +* **core:** Optimize getSharedWorkflowIds query ([#6314](https://github.com/n8n-io/n8n/issues/6314)) ([0631f69](https://github.com/n8n-io/n8n/commit/0631f69d98e5420faebba1a54d9ad47a2664d110)) +* **core:** Prevent prototype pollution on injectable services ([#6309](https://github.com/n8n-io/n8n/issues/6309)) ([d94c20a](https://github.com/n8n-io/n8n/commit/d94c20ada543767f700475b40ef7174c433c26c5)) +* **editor:** Fix locale plularisation if count is 0 ([#6312](https://github.com/n8n-io/n8n/issues/6312)) ([0d88bd7](https://github.com/n8n-io/n8n/commit/0d88bd7c1ae95cf077c2fa231d942204ff3b8f68)) +* **editor:** Fix Luxon date parsing of ExecutionsUsage component ([#6333](https://github.com/n8n-io/n8n/issues/6333)) ([8f0ff46](https://github.com/n8n-io/n8n/commit/8f0ff460b11999f4d78f8313910358aa87311713)) +* **editor:** Update SSO settings styles ([#6342](https://github.com/n8n-io/n8n/issues/6342)) ([5ae1124](https://github.com/n8n-io/n8n/commit/5ae1124106e7597d0943c371eae6aba6c105fd6b)) +* **Execute Command Node:** Block executions when `command` is empty ([#6308](https://github.com/n8n-io/n8n/issues/6308)) ([011d577](https://github.com/n8n-io/n8n/commit/011d5778b15232cff94a321dfee18c3d7489f93d)) +* Show `Ask AI` only on Code Node ([#6336](https://github.com/n8n-io/n8n/issues/6336)) ([da856d1](https://github.com/n8n-io/n8n/commit/da856d1c6593b43e1ce8d1becb1464c19c908e78)) + + +### Features + +* Add manual login option and password reset link for SSO ([#6328](https://github.com/n8n-io/n8n/issues/6328)) ([77e3f15](https://github.com/n8n-io/n8n/commit/77e3f1551dd7473a69f8833be5678d98964142e1)) +* **core:** Add metadata (customdata) to event log ([#6334](https://github.com/n8n-io/n8n/issues/6334)) ([792b1c1](https://github.com/n8n-io/n8n/commit/792b1c1ffb0eb279bc3451787891ca3835f59d9f)) +* **editor:** Implement Resource Mapper component ([#6207](https://github.com/n8n-io/n8n/issues/6207)) ([04cfa54](https://github.com/n8n-io/n8n/commit/04cfa548af3c7a25f1f0a36ddfb1de6a9e3f2169)), closes [#5752](https://github.com/n8n-io/n8n/issues/5752) [#5814](https://github.com/n8n-io/n8n/issues/5814) + + + # [0.230.0](https://github.com/n8n-io/n8n/compare/n8n@0.229.0...n8n@0.230.0) (2023-05-24) diff --git a/cypress/e2e/22-user-activation-modal.cy.ts b/cypress/e2e/22-user-activation-modal.cy.ts deleted file mode 100644 index e9a730623d25e..0000000000000 --- a/cypress/e2e/22-user-activation-modal.cy.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { WorkflowPage, NDV, UserActivationSurveyModal } from '../pages'; -import SettingsWithActivationModalEnabled from '../fixtures/Settings_user_activation_modal_enabled.json'; -import { v4 as uuid } from 'uuid'; - -const workflowPage = new WorkflowPage(); -const ndv = new NDV(); -const userActivationSurveyModal = new UserActivationSurveyModal(); - -const BASE_WEBHOOK_URL = 'http://localhost:5678/webhook'; - -describe('User activation survey', () => { - before(() => { - cy.skipSetup(); - }); - - it('Should show activation survey', () => { - cy.intercept('GET', '/rest/settings', (req) => { - req.reply(SettingsWithActivationModalEnabled); - }); - - const path = uuid(); - const method = 'GET'; - - workflowPage.actions.addInitialNodeToCanvas('Webhook'); - workflowPage.actions.openNode('Webhook'); - - //input http method - cy.getByTestId('parameter-input-httpMethod').click(); - cy.getByTestId('parameter-input-httpMethod') - .find('.el-select-dropdown') - .find('.option-headline') - .contains(method) - .click(); - - //input path method - cy.getByTestId('parameter-input-path') - .find('.parameter-input') - .find('input') - .clear() - .type(path); - - ndv.actions.close(); - - workflowPage.actions.saveWorkflowOnButtonClick(); - - workflowPage.actions.activateWorkflow(); - - cy.intercept('GET', '/rest/workflows').as('getWorkflows'); - cy.intercept('GET', '/rest/credentials').as('getCredentials'); - cy.intercept('GET', '/rest/active').as('getActive'); - - cy.request(method, `${BASE_WEBHOOK_URL}/${path}`).then((response) => { - expect(response.status).to.eq(200); - cy.visit('/'); - cy.reload(); - - cy.wait(['@getWorkflows', '@getCredentials', '@getActive']); - userActivationSurveyModal.getters.modalContainer().should('be.visible'); - userActivationSurveyModal.getters.feedbackInput().should('be.visible'); - userActivationSurveyModal.getters.skipButton().should('be.visible'); - userActivationSurveyModal.getters.feedbackInput().type('testing'); - userActivationSurveyModal.getters.feedbackInput().should('have.value', 'testing'); - userActivationSurveyModal.getters.sendFeedbackButton().click(); - }); - }); -}); diff --git a/cypress/fixtures/Settings_user_activation_modal_enabled.json b/cypress/fixtures/Settings_user_activation_modal_enabled.json deleted file mode 100644 index 8ce6a5fca67e2..0000000000000 --- a/cypress/fixtures/Settings_user_activation_modal_enabled.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "data": { - "endpointWebhook": "webhook", - "endpointWebhookTest": "webhook-test", - "saveDataErrorExecution": "all", - "saveDataSuccessExecution": "all", - "saveManualExecutions": false, - "executionTimeout": -1, - "maxExecutionTimeout": 3600, - "workflowCallerPolicyDefaultOption": "workflowsFromSameOwner", - "timezone": "America/New_York", - "urlBaseWebhook": "http://localhost:5678/", - "urlBaseEditor": "http://localhost:5678", - "versionCli": "0.221.2", - "oauthCallbackUrls": { - "oauth1": "http://localhost:5678/rest/oauth1-credential/callback", - "oauth2": "http://localhost:5678/rest/oauth2-credential/callback" - }, - "versionNotifications": { - "enabled": true, - "endpoint": "https://api.n8n.io/api/versions/", - "infoUrl": "https://docs.n8n.io/release-notes/" - }, - "instanceId": "c229842c6d1e217486d04caf7223758e08385156ca87a58286c850760c7161f4", - "telemetry": { - "enabled": true - }, - "posthog": { - "enabled": false, - "apiHost": "https://ph.n8n.io", - "apiKey": "phc_4URIAm1uYfJO7j8kWSe0J8lc8IqnstRLS7Jx8NcakHo", - "autocapture": false, - "disableSessionRecording": true, - "debug": false - }, - "personalizationSurveyEnabled": false, - "userActivationSurveyEnabled": true, - "defaultLocale": "en", - "userManagement": { - "enabled": true, - "showSetupOnFirstLoad": false, - "smtpSetup": false - }, - "sso": { - "saml": { - "loginEnabled": false, - "loginLabel": "" - }, - "ldap": { - "loginEnabled": false, - "loginLabel": "" - } - }, - "publicApi": { - "enabled": false, - "latestVersion": 1, - "path": "api", - "swaggerUi": { - "enabled": true - } - }, - "workflowTagsDisabled": false, - "logLevel": "info", - "hiringBannerEnabled": true, - "templates": { - "enabled": true, - "host": "https://api.n8n.io/api/" - }, - "onboardingCallPromptEnabled": true, - "executionMode": "regular", - "pushBackend": "sse", - "communityNodesEnabled": true, - "deployment": { - "type": "default" - }, - "isNpmAvailable": false, - "allowedModules": {}, - "enterprise": { - "sharing": true, - "ldap": true, - "saml": false, - "logStreaming": false, - "advancedExecutionFilters": false - }, - "hideUsagePage": false, - "license": { - "environment": "production" - } - } -} diff --git a/cypress/pages/modals/index.ts b/cypress/pages/modals/index.ts index 358ba0cf7800d..3d1981d027ad2 100644 --- a/cypress/pages/modals/index.ts +++ b/cypress/pages/modals/index.ts @@ -1,5 +1,3 @@ export * from './credentials-modal'; export * from './message-box'; export * from './workflow-sharing-modal'; -export * from './user-activation-survey-modal'; - diff --git a/cypress/pages/modals/user-activation-survey-modal.ts b/cypress/pages/modals/user-activation-survey-modal.ts deleted file mode 100644 index cc8f139fb614c..0000000000000 --- a/cypress/pages/modals/user-activation-survey-modal.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { BasePage } from './../base'; - -export class UserActivationSurveyModal extends BasePage { - getters = { - modalContainer: () => cy.getByTestId('userActivationSurvey-modal').last(), - feedbackInput: () => cy.getByTestId('activation-feedback-input').find('textarea'), - sendFeedbackButton: () => cy.getByTestId('send-activation-feedback-button'), - skipButton: () => cy.getByTestId('skip-button'), - }; -} diff --git a/package.json b/package.json index 2c6115a46096e..3451685dbfac8 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "n8n", - "version": "0.230.0", + "version": "0.231.0", "private": true, "homepage": "https://n8n.io", "engines": { "node": ">=16.9", - "pnpm": ">=8.1" + "pnpm": ">=8.6" }, - "packageManager": "pnpm@8.1.0", + "packageManager": "pnpm@8.6.1", "scripts": { "preinstall": "node scripts/block-npm-install.js", "build": "turbo run build", @@ -34,6 +34,8 @@ "test:e2e:all": "cross-env E2E_TESTS=true NODE_OPTIONS=--dns-result-order=ipv4first start-server-and-test start http://localhost:5678/favicon.ico 'cypress run --headless'" }, "dependencies": { + "@elastic/elasticsearch": "^8.8.0", + "aws-sdk": "^2.1394.0", "n8n": "workspace:*" }, "devDependencies": { @@ -86,7 +88,7 @@ "prettier": "^2.8.3", "tslib": "^2.5.0", "ts-node": "^10.9.1", - "typescript": "^5.0.3", + "typescript": "^5.1.3", "xml2js": "^0.5.0", "cpy@8>globby": "^11.1.0", "qqjs>globby": "^11.1.0" diff --git a/packages/cli/BREAKING-CHANGES.md b/packages/cli/BREAKING-CHANGES.md index 47e17480d1179..2269ed9456e92 100644 --- a/packages/cli/BREAKING-CHANGES.md +++ b/packages/cli/BREAKING-CHANGES.md @@ -7,6 +7,7 @@ This list shows all the versions which include breaking changes and how to upgra ### What changed? Due to Node.js/OpenSSL upgrade, the following crypto algorithms are not supported anymore. + - RSA-MD4 - RSA-MDC2 - md4 @@ -18,6 +19,18 @@ Due to Node.js/OpenSSL upgrade, the following crypto algorithms are not supporte If you're using any of the above mentioned crypto algorithms in Crypto node in any of your workflows, then please update the algorithm property in the node to one of the supported values. +### What changed? + +The `LoneScale List` node has been renamed to `LoneScale`. + +### When is action necessary? + +If you have used the `LoneScale List` node in any of your workflows. + +### How to upgrade: + +Update any workflows using `LoneScale List` to use the updated node. + ## 0.226.0 ### What changed? diff --git a/packages/cli/package.json b/packages/cli/package.json index 02817de2e304e..f5c36aff4c838 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.230.0", + "version": "0.231.0", "description": "n8n Workflow Automation Tool", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", diff --git a/packages/cli/src/License.ts b/packages/cli/src/License.ts index e241f5fbd18c6..b5e40e21cb870 100644 --- a/packages/cli/src/License.ts +++ b/packages/cli/src/License.ts @@ -122,7 +122,7 @@ export class License { } isAdvancedExecutionFiltersEnabled() { - return this.isFeatureEnabled(LICENSE_FEATURES.ADVANCED_EXECUTION_FILTERS); + return true; } isVariablesEnabled() { diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 0f125c256198f..d0abb397afc43 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -258,8 +258,6 @@ export class Server extends AbstractServer { }, personalizationSurveyEnabled: config.getEnv('personalization.enabled') && config.getEnv('diagnostics.enabled'), - userActivationSurveyEnabled: - config.getEnv('userActivationSurvey.enabled') && config.getEnv('diagnostics.enabled'), defaultLocale: config.getEnv('defaultLocale'), userManagement: { enabled: isUserManagementEnabled(), @@ -1248,7 +1246,7 @@ export class Server extends AbstractServer { throw new ResponseHelper.NotFoundError('Execution not found'); } - const execution = await Db.collections.Execution.findOne({ + const execution = await Db.collections.Execution.exist({ where: { id: executionId, workflowId: In(sharedWorkflowIds), diff --git a/packages/cli/src/WaitTracker.ts b/packages/cli/src/WaitTracker.ts index 91e1051b35d5b..0b9902b3b2331 100644 --- a/packages/cli/src/WaitTracker.ts +++ b/packages/cli/src/WaitTracker.ts @@ -18,11 +18,13 @@ import * as Db from '@/Db'; import * as ResponseHelper from '@/ResponseHelper'; import type { IExecutionFlattedDb, + IExecutionResponse, IExecutionsStopData, IWorkflowExecutionDataProcess, } from '@/Interfaces'; import { WorkflowRunner } from '@/WorkflowRunner'; import { getWorkflowOwner } from '@/UserManagement/UserManagementHelper'; +import { recoverExecutionDataFromEventLogMessages } from './eventbus/MessageEventBus/recoverEvents'; @Service() export class WaitTracker { @@ -106,12 +108,29 @@ export class WaitTracker { // Also check in database const execution = await Db.collections.Execution.findOneBy({ id: executionId }); - if (execution === null || !execution.waitTill) { + if (execution === null) { throw new Error(`The execution ID "${executionId}" could not be found.`); } - const fullExecutionData = ResponseHelper.unflattenExecutionData(execution); - + if (!['new', 'unknown', 'waiting', 'running'].includes(execution.status)) { + throw new Error( + `Only running or waiting executions can be stopped and ${executionId} is currently ${execution.status}.`, + ); + } + let fullExecutionData: IExecutionResponse; + try { + fullExecutionData = ResponseHelper.unflattenExecutionData(execution); + } catch (error) { + // if the execution ended in an unforseen, non-cancelable state, try to recover it + await recoverExecutionDataFromEventLogMessages(executionId, [], true); + // find recovered data + const recoveredExecution = await Db.collections.Execution.findOneBy({ id: executionId }); + if (recoveredExecution) { + fullExecutionData = ResponseHelper.unflattenExecutionData(recoveredExecution); + } else { + throw new Error(`Execution ${executionId} could not be recovered or canceled.`); + } + } // Set in execution in DB as failed and remove waitTill time const error = new WorkflowOperationError('Workflow-Execution has been canceled!'); diff --git a/packages/cli/src/commands/Interfaces.d.ts b/packages/cli/src/commands/Interfaces.d.ts index e06bb83abd495..765b16d7aabf6 100644 --- a/packages/cli/src/commands/Interfaces.d.ts +++ b/packages/cli/src/commands/Interfaces.d.ts @@ -1,5 +1,6 @@ interface IResult { totalWorkflows: number; + slackMessage: string; summary: { failedExecutions: number; successfulExecutions: number; diff --git a/packages/cli/src/commands/executeBatch.ts b/packages/cli/src/commands/executeBatch.ts index f0c60bf20f749..413a4894da03e 100644 --- a/packages/cli/src/commands/executeBatch.ts +++ b/packages/cli/src/commands/executeBatch.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-loop-func */ import fs from 'fs'; +import os from 'os'; import { flags } from '@oclif/command'; import type { ITaskData } from 'n8n-workflow'; import { sleep } from 'n8n-workflow'; @@ -35,6 +36,8 @@ export class ExecuteBatch extends BaseCommand { static concurrency = 1; + static githubWorkflow = false; + static debug = false; static executionTimeout = 3 * 60 * 1000; @@ -80,6 +83,12 @@ export class ExecuteBatch extends BaseCommand { description: 'Compares only if attributes output from node are the same, with no regards to nested JSON objects.', }), + + githubWorkflow: flags.boolean({ + description: + 'Enables more lenient comparison for GitHub workflows. This is useful for reducing false positives when comparing Test workflows.', + }), + skipList: flags.string({ description: 'File containing a comma separated list of workflow IDs to skip.', }), @@ -175,7 +184,6 @@ export class ExecuteBatch extends BaseCommand { async run() { // eslint-disable-next-line @typescript-eslint/no-shadow const { flags } = this.parse(ExecuteBatch); - ExecuteBatch.debug = flags.debug; ExecuteBatch.concurrency = flags.concurrency || 1; @@ -251,6 +259,10 @@ export class ExecuteBatch extends BaseCommand { ExecuteBatch.shallow = true; } + if (flags.githubWorkflow) { + ExecuteBatch.githubWorkflow = true; + } + ExecuteBatch.instanceOwner = await getInstanceOwner(); const query = Db.collections.Workflow.createQueryBuilder('workflows'); @@ -369,6 +381,7 @@ export class ExecuteBatch extends BaseCommand { private async runTests(allWorkflows: IWorkflowDb[]): Promise { const result: IResult = { totalWorkflows: allWorkflows.length, + slackMessage: '', summary: { failedExecutions: 0, warningExecutions: 0, @@ -472,11 +485,30 @@ export class ExecuteBatch extends BaseCommand { } await Promise.allSettled(promisesArray); - + if (ExecuteBatch.githubWorkflow) { + if (result.summary.errors.length < 6) { + const errorMessage = result.summary.errors.map((error) => { + return `*${error.workflowId}*: ${error.error}`; + }); + result.slackMessage = `*${ + result.summary.errors.length + } Executions errors*. Workflows failing: ${errorMessage.join(' ')} `; + } else { + result.slackMessage = `*${result.summary.errors.length} Executions errors*`; + } + this.setOutput('slackMessage', JSON.stringify(result.slackMessage)); + } res(result); }); } + setOutput(key: string, value: any) { + // Temporary hack until we move to the new action. + const output = process.env.GITHUB_OUTPUT; + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + fs.appendFileSync(output as unknown as fs.PathOrFileDescriptor, `${key}=${value}${os.EOL}`); + } + updateStatus() { if (ExecuteBatch.cancelled) { return; @@ -756,8 +788,13 @@ export class ExecuteBatch extends BaseCommand { // and search for the `__deleted` string const changesJson = JSON.stringify(changes); if (changesJson.includes('__deleted')) { - // we have structural changes. Report them. - executionResult.error = 'Workflow may contain breaking changes'; + if (ExecuteBatch.githubWorkflow) { + const deletedChanges = changesJson.match(/__deleted/g) ?? []; + // we have structural changes. Report them. + executionResult.error = `Workflow contains ${deletedChanges.length} deleted data.`; + } else { + executionResult.error = 'Workflow may contain breaking changes'; + } executionResult.changes = changes; executionResult.executionStatus = 'error'; } else { diff --git a/packages/cli/src/config/schema.ts b/packages/cli/src/config/schema.ts index 38ab5da66cb3f..f6b2175dcb6a3 100644 --- a/packages/cli/src/config/schema.ts +++ b/packages/cli/src/config/schema.ts @@ -1043,15 +1043,6 @@ export const schema = { }, }, - userActivationSurvey: { - enabled: { - doc: 'Whether user activation survey is enabled.', - format: Boolean, - default: true, - env: 'N8N_USER_ACTIVATION_SURVEY_ENABLED', - }, - }, - diagnostics: { enabled: { doc: 'Whether diagnostic mode is enabled.', diff --git a/packages/cli/src/databases/entities/ExecutionEntity.ts b/packages/cli/src/databases/entities/ExecutionEntity.ts index c16365bbcf638..0028bc424a4db 100644 --- a/packages/cli/src/databases/entities/ExecutionEntity.ts +++ b/packages/cli/src/databases/entities/ExecutionEntity.ts @@ -5,6 +5,7 @@ import { IWorkflowDb } from '@/Interfaces'; import type { IExecutionFlattedDb } from '@/Interfaces'; import { idStringifier } from '../utils/transformers'; import type { ExecutionMetadata } from './ExecutionMetadata'; +import { IsOptional } from 'class-validator'; @Entity() @Index(['workflowId', 'id']) diff --git a/packages/cli/src/elasticSearchCli.ts b/packages/cli/src/elasticSearchCli.ts new file mode 100644 index 0000000000000..e1deade162229 --- /dev/null +++ b/packages/cli/src/elasticSearchCli.ts @@ -0,0 +1,57 @@ +import { LoggerProxy as Logger } from 'n8n-workflow'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { Client } from '@elastic/elasticsearch'; + +export class ElasticSearchClient { + index: string; + + client: Client; + + constructor() { + this.client = this.setupClient(); + this.index = process.env.FOUNDELASTICSEARCH_INDEX ?? '0'; + } + + searchDocuments = async (text: string): Promise => { + try { + Logger.debug(` Elastic search advanced search: Looking for executions with string: ${text}`); + const { hits }: any = await this.client.search({ + index: this.index, + body: { + query: { + query_string: { + query: `*${text}*`, + }, + }, + }, + }); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-return + const executionIds = hits?.hits?.map((hit: any) => hit._id); + Logger.debug( + ` Elastic search advanced search: Relevant executions for text: ${text}, execution list: ${executionIds}`, + ); + return executionIds; + } catch (error) { + Logger.error(`Elastic search advanced search: Error searching documents:${error}`); + } + }; + + setupClient = (): Client => { + const clientUrl: string = process.env.FOUNDELASTICSEARCH_URL ?? 'http://localhost:9200'; + if (clientUrl === 'http://localhost:9200') { + return new Client({ node: clientUrl }); + } else { + const apiKey: string = process.env.FOUNDELASTICSEARCH_APIKEY ?? ''; + if (!apiKey) { + Logger.warn(' Elastic search is not local and an api key is not configured'); + } + return new Client({ + node: clientUrl, + auth: { + apiKey, + }, + }); + } + }; +} diff --git a/packages/cli/src/environments/versionControl/types/versionControlPreferences.ts b/packages/cli/src/environments/versionControl/types/versionControlPreferences.ts index c17e334a7ca72..829cd0a176b57 100644 --- a/packages/cli/src/environments/versionControl/types/versionControlPreferences.ts +++ b/packages/cli/src/environments/versionControl/types/versionControlPreferences.ts @@ -44,12 +44,12 @@ export class VersionControlPreferences { ): VersionControlPreferences { return new VersionControlPreferences({ connected: preferences.connected ?? defaultPreferences.connected, - authorEmail: preferences.authorEmail ?? defaultPreferences.authorEmail, + repositoryUrl: preferences.repositoryUrl ?? defaultPreferences.repositoryUrl, authorName: preferences.authorName ?? defaultPreferences.authorName, + authorEmail: preferences.authorEmail ?? defaultPreferences.authorEmail, branchName: preferences.branchName ?? defaultPreferences.branchName, - branchColor: preferences.branchColor ?? defaultPreferences.branchColor, branchReadOnly: preferences.branchReadOnly ?? defaultPreferences.branchReadOnly, - repositoryUrl: preferences.repositoryUrl ?? defaultPreferences.repositoryUrl, + branchColor: preferences.branchColor ?? defaultPreferences.branchColor, }); } } diff --git a/packages/cli/src/environments/versionControl/versionControl.controller.ee.ts b/packages/cli/src/environments/versionControl/versionControl.controller.ee.ts index a85daa2ca7f4e..65da1b773e3bd 100644 --- a/packages/cli/src/environments/versionControl/versionControl.controller.ee.ts +++ b/packages/cli/src/environments/versionControl/versionControl.controller.ee.ts @@ -1,4 +1,4 @@ -import { Authorized, Get, Post, RestController } from '@/decorators'; +import { Authorized, Get, Post, Patch, RestController } from '@/decorators'; import { versionControlLicensedMiddleware, versionControlLicensedAndEnabledMiddleware, @@ -83,10 +83,38 @@ export class VersionControlController { } @Authorized(['global', 'owner']) - @Post('/set-read-only', { middlewares: [versionControlLicensedMiddleware] }) - async setReadOnly(req: VersionControlRequest.SetReadOnly) { + @Patch('/preferences', { middlewares: [versionControlLicensedMiddleware] }) + async updatePreferences(req: VersionControlRequest.UpdatePreferences) { try { - this.versionControlPreferencesService.setBranchReadOnly(req.body.branchReadOnly); + const sanitizedPreferences: Partial = { + ...req.body, + initRepo: false, + connected: undefined, + publicKey: undefined, + repositoryUrl: undefined, + authorName: undefined, + authorEmail: undefined, + }; + const currentPreferences = this.versionControlPreferencesService.getPreferences(); + await this.versionControlPreferencesService.validateVersionControlPreferences( + sanitizedPreferences, + ); + if ( + sanitizedPreferences.branchName && + sanitizedPreferences.branchName !== currentPreferences.branchName + ) { + await this.versionControlService.setBranch(sanitizedPreferences.branchName); + } + if (sanitizedPreferences.branchColor || sanitizedPreferences.branchReadOnly !== undefined) { + await this.versionControlPreferencesService.setPreferences( + { + branchColor: sanitizedPreferences.branchColor, + branchReadOnly: sanitizedPreferences.branchReadOnly, + }, + true, + ); + } + await this.versionControlService.init(); return this.versionControlPreferencesService.getPreferences(); } catch (error) { throw new BadRequestError((error as { message: string }).message); @@ -113,16 +141,6 @@ export class VersionControlController { } } - @Authorized(['global', 'owner']) - @Post('/set-branch', { middlewares: [versionControlLicensedMiddleware] }) - async setBranch(req: VersionControlRequest.SetBranch) { - try { - return await this.versionControlService.setBranch(req.body.branch); - } catch (error) { - throw new BadRequestError((error as { message: string }).message); - } - } - @Authorized(['global', 'owner']) @Post('/push-workfolder', { middlewares: [versionControlLicensedAndEnabledMiddleware] }) async pushWorkfolder( diff --git a/packages/cli/src/environments/versionControl/versionControl.service.ee.ts b/packages/cli/src/environments/versionControl/versionControl.service.ee.ts index 7a37cfe357ef7..a2c2a307095a7 100644 --- a/packages/cli/src/environments/versionControl/versionControl.service.ee.ts +++ b/packages/cli/src/environments/versionControl/versionControl.service.ee.ts @@ -172,7 +172,7 @@ export class VersionControlService { async setBranch(branch: string): Promise<{ branches: string[]; currentBranch: string }> { await this.versionControlPreferencesService.setPreferences({ branchName: branch, - connected: true, + connected: branch?.length > 0, }); return this.gitService.setBranch(branch); } @@ -216,40 +216,6 @@ export class VersionControlService { }); } - // async pushWorkfolder( - // options: VersionControlPushWorkFolder, - // ): Promise { - // await this.gitService.fetch(); - // await this.export(); // refresh workfolder - // await this.stage(options); - // await this.gitService.commit(options.message ?? 'Updated Workfolder'); - // return this.gitService.push({ - // branch: this.versionControlPreferencesService.getBranchName(), - // force: options.force ?? false, - // }); - // } - - // TODO: Alternate implementation for pull - // async pullWorkfolder( - // options: VersionControllPullOptions, - // ): Promise { - // const diffResult = await this.getStatus(); - // const possibleConflicts = diffResult?.filter((file) => file.conflict); - // if (possibleConflicts?.length > 0 || options.force === true) { - // await this.unstage(); - // if (options.force === true) { - // return this.resetWorkfolder(options); - // } else { - // return diffResult; - // } - // } - // const pullResult = await this.gitService.pull(); - // if (options.importAfterPull) { - // return this.import(options); - // } - // return pullResult; - // } - async pullWorkfolder( options: VersionControllPullOptions, ): Promise { diff --git a/packages/cli/src/environments/versionControl/versionControlPreferences.service.ee.ts b/packages/cli/src/environments/versionControl/versionControlPreferences.service.ee.ts index 6233f53f97abe..c17ba8d7f665f 100644 --- a/packages/cli/src/environments/versionControl/versionControlPreferences.service.ee.ts +++ b/packages/cli/src/environments/versionControl/versionControlPreferences.service.ee.ts @@ -114,19 +114,10 @@ export class VersionControlPreferencesService { return this.versionControlPreferences; } - setBranchReadOnly(branchReadOnly: boolean): void { - this._versionControlPreferences.branchReadOnly = branchReadOnly; - } - async validateVersionControlPreferences( preferences: Partial, allowMissingProperties = true, ): Promise { - if (this.isVersionControlConnected()) { - if (preferences.repositoryUrl !== this._versionControlPreferences.repositoryUrl) { - throw new Error('Cannot change repository while connected'); - } - } const preferencesObject = new VersionControlPreferences(preferences); const validationResult = await validate(preferencesObject, { forbidUnknownValues: false, diff --git a/packages/cli/src/events/WorkflowStatistics.ts b/packages/cli/src/events/WorkflowStatistics.ts index 6a500399c5026..b2a15b491912c 100644 --- a/packages/cli/src/events/WorkflowStatistics.ts +++ b/packages/cli/src/events/WorkflowStatistics.ts @@ -118,7 +118,6 @@ export async function workflowExecutionCompleted( await UserService.updateUserSettings(owner.id, { firstSuccessfulWorkflowId: workflowId, userActivated: true, - showUserActivationSurvey: true, }); } diff --git a/packages/cli/src/executions/executions.service.ts b/packages/cli/src/executions/executions.service.ts index 41372335f04e2..ff8e11d6d58c7 100644 --- a/packages/cli/src/executions/executions.service.ts +++ b/packages/cli/src/executions/executions.service.ts @@ -41,6 +41,7 @@ import { } from './executionHelpers'; import { ExecutionMetadata } from '@db/entities/ExecutionMetadata'; import { DateUtils } from 'typeorm/util/DateUtils'; +import { ElasticSearchClient } from '@/elasticSearchCli'; interface IGetExecutionsQueryFilter { id?: FindOperator | string; @@ -55,6 +56,7 @@ interface IGetExecutionsQueryFilter { metadata?: Array<{ key: string; value: string }>; startedAfter?: string; startedBefore?: string; + advancedSearch?: string; } const schemaGetExecutionsQueryFilter = { @@ -75,6 +77,7 @@ const schemaGetExecutionsQueryFilter = { metadata: { type: 'array', items: { $ref: '#/$defs/metadata' } }, startedAfter: { type: 'date-time' }, startedBefore: { type: 'date-time' }, + advancedSearch: { type: 'string' }, }, $defs: { metadata: { @@ -322,13 +325,13 @@ export class ExecutionsService { .take(limit) .where(findWhere); - const countFilter = deepCopy(filter ?? {}); + let countFilter = deepCopy(filter ?? {}); const metadata = isAdvancedExecutionFiltersEnabled() ? filter?.metadata : undefined; if (metadata?.length) { query = query.leftJoin(ExecutionMetadata, 'md', 'md.executionId = execution.id'); for (const md of metadata) { - query = query.andWhere('md.key = :key AND md.value = :value', md); + query = query.andWhere('md.key = :key AND md.value ~ :value', md); } } @@ -354,6 +357,21 @@ export class ExecutionsService { Object.assign(countFilter, { status: In(filter.status) }); } + let advancedSearchValue: string | undefined; + let advancedFilteredExecutions: any; + + if (filter?.advancedSearch) { + // advancedSearchValue is not a real field in the database so we want to make + // sure we're saving it in memory and not passing it down to the database query. + advancedSearchValue = filter.advancedSearch; + filter.advancedSearch = undefined; + delete filter.advancedSearch; + delete countFilter.advancedSearch; + // Calling Elastic to retrieve all the relevant docs + const client = new ElasticSearchClient(); + advancedFilteredExecutions = (await client.searchDocuments(advancedSearchValue)) || []; + } + if (filter) { this.massageFilters(filter as IDataObject); query = query.andWhere(filter); @@ -362,8 +380,12 @@ export class ExecutionsService { this.massageFilters(countFilter as IDataObject); countFilter.id = Not(In(executingWorkflowIds)); - const executions = await query.getMany(); + let executions = await query.getMany(); + if (advancedFilteredExecutions) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call + executions = executions.filter((obj) => advancedFilteredExecutions.includes(obj.id)); + } const { count, estimated } = await this.getExecutionsCount( countFilter as IDataObject, req.user, diff --git a/packages/cli/src/requests.ts b/packages/cli/src/requests.ts index 498d56c3926ad..9b4adf94ad36c 100644 --- a/packages/cli/src/requests.ts +++ b/packages/cli/src/requests.ts @@ -33,10 +33,6 @@ export class UserUpdatePayload implements Pick { + const document = { + index: this.index, + id: executionId, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + body: data, + }; + try { + await this.client.index(document); + Logger.debug(`ElasticSearch: Adding Execution:${executionId} to index:${this.index}`); + } catch (error) { + Logger.error( + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `ElasticSearch: Was not able to add Execution:${executionId} to index:${this.index}, error: ${error}`, + ); + } + }; + + setupClient = (): Client => { + const clientUrl: string = process.env.FOUNDELASTICSEARCH_URL ?? 'http://localhost:9200'; + if (clientUrl === 'http://localhost:9200') { + return new Client({ node: clientUrl }); + } else { + const apiKey: string = process.env.FOUNDELASTICSEARCH_APIKEY ?? ''; + if (!apiKey) { + Logger.warn(' Elastic search is not local and an api key is not configured'); + } + return new Client({ + node: clientUrl, + auth: { + apiKey, + }, + }); + } + }; +} diff --git a/packages/design-system/package.json b/packages/design-system/package.json index 9356f471b3b8b..6b7ff38e692eb 100644 --- a/packages/design-system/package.json +++ b/packages/design-system/package.json @@ -1,6 +1,6 @@ { "name": "n8n-design-system", - "version": "0.66.0", + "version": "0.67.0", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", "author": { @@ -49,6 +49,7 @@ "@storybook/vue": "^7.0.7", "@storybook/vue-webpack5": "^7.0.7", "@testing-library/jest-dom": "^5.16.5", + "@testing-library/user-event": "^14.4.3", "@testing-library/vue": "^5.8.3", "@types/markdown-it": "^12.2.3", "@types/markdown-it-emoji": "^2.0.2", diff --git a/packages/design-system/src/components/N8nMenu/Menu.vue b/packages/design-system/src/components/N8nMenu/Menu.vue index 6dafe805fcd4b..62c45740993ab 100644 --- a/packages/design-system/src/components/N8nMenu/Menu.vue +++ b/packages/design-system/src/components/N8nMenu/Menu.vue @@ -193,9 +193,4 @@ export default defineComponent({ display: none !important; } } - -.menuPrefix, -.menuSuffix { - padding: var(--spacing-xs) var(--spacing-l); -} diff --git a/packages/design-system/src/components/N8nSelect/__tests__/Select.spec.ts b/packages/design-system/src/components/N8nSelect/__tests__/Select.spec.ts index 1c4084d869fb2..87a96883081b0 100644 --- a/packages/design-system/src/components/N8nSelect/__tests__/Select.spec.ts +++ b/packages/design-system/src/components/N8nSelect/__tests__/Select.spec.ts @@ -1,4 +1,6 @@ -import { render } from '@testing-library/vue'; +import { defineComponent, ref } from 'vue'; +import { render, waitFor, within } from '@testing-library/vue'; +import userEvent from '@testing-library/user-event'; import N8nSelect from '../Select.vue'; import N8nOption from '../../N8nOption/Option.vue'; @@ -19,5 +21,38 @@ describe('components', () => { }); expect(wrapper.html()).toMatchSnapshot(); }); + + it('should select an option', async () => { + const n8nSelectTestComponent = defineComponent({ + components: { + N8nSelect, + N8nOption, + }, + template: ` + + + + `, + setup() { + const options = ref(['1', '2', '3']); + const selected = ref(''); + + return { + options, + selected, + }; + }, + }); + + const { container, getByRole } = render(n8nSelectTestComponent); + const getOption = (value: string) => within(container as HTMLElement).getByText(value); + const textbox = getByRole('textbox'); + + await userEvent.click(textbox); + await waitFor(() => expect(getOption('1')).toBeVisible()); + await userEvent.click(getOption('1')); + + expect(textbox).toHaveValue('1'); + }); }); }); diff --git a/packages/design-system/src/utils/__tests__/event-bus.spec.ts b/packages/design-system/src/utils/__tests__/event-bus.spec.ts index e403b61008f4e..a5dba6f833011 100644 --- a/packages/design-system/src/utils/__tests__/event-bus.spec.ts +++ b/packages/design-system/src/utils/__tests__/event-bus.spec.ts @@ -1,5 +1,11 @@ import { createEventBus } from '../event-bus'; +// @TODO: Remove when conflicting vitest and jest globals are reconciled +declare global { + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + const vi: typeof import('vitest')['vitest']; +} + describe('createEventBus()', () => { const eventBus = createEventBus(); diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index a94585fcc0ab1..ec529eb878586 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -1,6 +1,6 @@ { "name": "n8n-editor-ui", - "version": "0.196.0", + "version": "0.197.0", "description": "Workflow Editor UI for n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", @@ -47,7 +47,6 @@ "@jsplumb/core": "^5.13.2", "@jsplumb/util": "^5.13.2", "axios": "^0.21.1", - "canvas-confetti": "^1.6.0", "codemirror-lang-html-n8n": "^1.0.0", "codemirror-lang-n8n-expression": "^0.2.0", "copy-to-clipboard": "^3.3.3", diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index 6b4e872bec718..2266bc52272a0 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -1390,6 +1390,7 @@ export type ExecutionFilterType = { endDate: string | Date; tags: string[]; metadata: ExecutionFilterMetadata[]; + advancedSearch: string; }; export type ExecutionsQueryFilter = { @@ -1400,6 +1401,7 @@ export type ExecutionsQueryFilter = { metadata?: Array<{ key: string; value: string }>; startedAfter?: string; startedBefore?: string; + advancedSearch?: string; }; export type SamlAttributeMapping = { diff --git a/packages/editor-ui/src/__tests__/server/endpoints/index.ts b/packages/editor-ui/src/__tests__/server/endpoints/index.ts index d79a81d30883b..911ca9c11f6cc 100644 --- a/packages/editor-ui/src/__tests__/server/endpoints/index.ts +++ b/packages/editor-ui/src/__tests__/server/endpoints/index.ts @@ -5,6 +5,7 @@ import { routesForCredentialTypes } from './credentialType'; import { routesForVariables } from './variable'; import { routesForSettings } from './settings'; import { routesForSSO } from './sso'; +import { routesForVersionControl } from './versionControl'; const endpoints: Array<(server: Server) => void> = [ routesForCredentials, @@ -13,6 +14,7 @@ const endpoints: Array<(server: Server) => void> = [ routesForVariables, routesForSettings, routesForSSO, + routesForVersionControl, ]; export { endpoints }; diff --git a/packages/editor-ui/src/__tests__/server/endpoints/settings.ts b/packages/editor-ui/src/__tests__/server/endpoints/settings.ts index 91cad386cc44d..af9079dda0214 100644 --- a/packages/editor-ui/src/__tests__/server/endpoints/settings.ts +++ b/packages/editor-ui/src/__tests__/server/endpoints/settings.ts @@ -71,7 +71,6 @@ const defaultSettings: IN8nUISettings = { variables: { limit: -1, }, - userActivationSurveyEnabled: false, deployment: { type: 'default', }, diff --git a/packages/editor-ui/src/__tests__/server/endpoints/versionControl.ts b/packages/editor-ui/src/__tests__/server/endpoints/versionControl.ts new file mode 100644 index 0000000000000..09a079d8691d4 --- /dev/null +++ b/packages/editor-ui/src/__tests__/server/endpoints/versionControl.ts @@ -0,0 +1,77 @@ +import type { Server, Request } from 'miragejs'; +import { Response } from 'miragejs'; +import { jsonParse } from 'n8n-workflow'; +import type { AppSchema } from '@/__tests__/server/types'; +import type { VersionControlPreferences } from '@/Interface'; + +export function routesForVersionControl(server: Server) { + const versionControlApiRoot = '/rest/version-control'; + const defaultVersionControlPreferences: VersionControlPreferences = { + branchName: '', + branches: [], + authorName: '', + authorEmail: '', + repositoryUrl: '', + branchReadOnly: false, + branchColor: '#1d6acb', + connected: false, + publicKey: 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHEX+25m', + }; + + server.post(`${versionControlApiRoot}/preferences`, (schema: AppSchema, request: Request) => { + const requestBody = jsonParse(request.requestBody) as Partial; + + return new Response( + 200, + {}, + { + data: { + ...defaultVersionControlPreferences, + ...requestBody, + }, + }, + ); + }); + + server.patch(`${versionControlApiRoot}/preferences`, (schema: AppSchema, request: Request) => { + const requestBody = jsonParse(request.requestBody) as Partial; + + return new Response( + 200, + {}, + { + data: { + ...defaultVersionControlPreferences, + ...requestBody, + }, + }, + ); + }); + + server.get(`${versionControlApiRoot}/get-branches`, () => { + return new Response( + 200, + {}, + { + data: { + branches: ['main', 'dev'], + currentBranch: 'main', + }, + }, + ); + }); + + server.post(`${versionControlApiRoot}/disconnect`, () => { + return new Response( + 200, + {}, + { + data: { + ...defaultVersionControlPreferences, + branchName: '', + connected: false, + }, + }, + ); + }); +} diff --git a/packages/editor-ui/src/__tests__/server/index.ts b/packages/editor-ui/src/__tests__/server/index.ts index f0dea12024c29..18ea0a4be8b67 100644 --- a/packages/editor-ui/src/__tests__/server/index.ts +++ b/packages/editor-ui/src/__tests__/server/index.ts @@ -23,14 +23,14 @@ export function setupServer() { // Enable logging server.logging = false; - // Handle undefined endpoints - server.post('/rest/:any', async () => new Promise(() => {})); - // Handle defined endpoints for (const endpointsFn of endpoints) { endpointsFn(server); } + // Handle undefined endpoints + server.post('/rest/:any', async () => ({})); + // Reset for everything else server.namespace = ''; server.passthrough(); diff --git a/packages/editor-ui/src/__tests__/utils.ts b/packages/editor-ui/src/__tests__/utils.ts index 4e59ae160aacb..6591bd070c588 100644 --- a/packages/editor-ui/src/__tests__/utils.ts +++ b/packages/editor-ui/src/__tests__/utils.ts @@ -31,7 +31,6 @@ export const waitAllPromises = async () => new Promise((resolve) => setTimeout(r export const SETTINGS_STORE_DEFAULT_STATE: ISettingsState = { settings: { - userActivationSurveyEnabled: false, allowedModules: {}, communityNodesEnabled: false, defaultLocale: '', diff --git a/packages/editor-ui/src/api/versionControl.ts b/packages/editor-ui/src/api/versionControl.ts index 299a3c3f82dce..a526ced47b7aa 100644 --- a/packages/editor-ui/src/api/versionControl.ts +++ b/packages/editor-ui/src/api/versionControl.ts @@ -9,6 +9,14 @@ import type { IDataObject } from 'n8n-workflow'; const versionControlApiRoot = '/version-control'; +const createPreferencesRequestFn = + (method: 'POST' | 'PATCH') => + async ( + context: IRestApiContext, + preferences: Partial, + ): Promise => + makeRestApiRequest(context, method, `${versionControlApiRoot}/preferences`, preferences); + export const pushWorkfolder = async ( context: IRestApiContext, data: IDataObject, @@ -29,19 +37,8 @@ export const getBranches = async ( return makeRestApiRequest(context, 'GET', `${versionControlApiRoot}/get-branches`); }; -export const setBranch = async ( - context: IRestApiContext, - branch: string, -): Promise<{ branches: string[]; currentBranch: string }> => { - return makeRestApiRequest(context, 'POST', `${versionControlApiRoot}/set-branch`, { branch }); -}; - -export const setPreferences = async ( - context: IRestApiContext, - preferences: Partial, -): Promise => { - return makeRestApiRequest(context, 'POST', `${versionControlApiRoot}/preferences`, preferences); -}; +export const savePreferences = createPreferencesRequestFn('POST'); +export const updatePreferences = createPreferencesRequestFn('PATCH'); export const getPreferences = async ( context: IRestApiContext, @@ -68,15 +65,6 @@ export const disconnect = async ( }); }; -export const setBranchReadonly = async ( - context: IRestApiContext, - branchReadOnly: boolean, -): Promise => { - return makeRestApiRequest(context, 'POST', `${versionControlApiRoot}/set-read-only`, { - branchReadOnly, - }); -}; - export const generateKeyPair = async (context: IRestApiContext): Promise => { return makeRestApiRequest(context, 'POST', `${versionControlApiRoot}/generate-key-pair`); }; diff --git a/packages/editor-ui/src/components/ExecutionFilter.vue b/packages/editor-ui/src/components/ExecutionFilter.vue index 7d0258ee83786..d011ed1ecd368 100644 --- a/packages/editor-ui/src/components/ExecutionFilter.vue +++ b/packages/editor-ui/src/components/ExecutionFilter.vue @@ -50,6 +50,7 @@ const getDefaultFilter = (): ExecutionFilterType => ({ startDate: '', endDate: '', metadata: [{ key: '', value: '' }], + advancedSearch: '', }); const filter = reactive(getDefaultFilter()); @@ -101,6 +102,9 @@ const countSelectedFilterProps = computed(() => { if (!!filter.endDate) { count++; } + if (!isEmpty(filter.advancedSearch)) { + count++; + } return count; }); @@ -130,6 +134,11 @@ const onTagsChange = (tags: string[]) => { emit('filterChanged', filter); }; +const onFilterAdvancedSearchChange = (advancedSearch: string) => { + filter.advancedSearch = advancedSearch; + debouncedEmit('filterChanged', filter); +}; + const onFilterReset = () => { Object.assign(filter, getDefaultFilter()); emit('filterChanged', filter); @@ -264,7 +273,7 @@ onBeforeMount(() => { - +