mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 04:34:06 -08:00
feat(editor): Node IO filter (#7503)
Co-authored-by: Omar Ajoue <krynble@gmail.com>
This commit is contained in:
parent
93103c0b08
commit
18817651ec
116
cypress/e2e/32-node-io-filter.cy.ts
Normal file
116
cypress/e2e/32-node-io-filter.cy.ts
Normal file
|
@ -0,0 +1,116 @@
|
|||
import { WorkflowPage as WorkflowPageClass, NDV } from '../pages';
|
||||
|
||||
const workflowPage = new WorkflowPageClass();
|
||||
const ndv = new NDV();
|
||||
|
||||
describe('Node IO Filter', () => {
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
cy.createFixtureWorkflow('Node_IO_filter.json', `Node IO filter`);
|
||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||
workflowPage.actions.executeWorkflow();
|
||||
});
|
||||
|
||||
it('should filter pinned data', () => {
|
||||
workflowPage.getters.canvasNodes().first().dblclick();
|
||||
ndv.actions.close();
|
||||
workflowPage.getters.canvasNodes().first().dblclick();
|
||||
cy.wait(500);
|
||||
ndv.getters.outputDataContainer().should('be.visible');
|
||||
cy.document().trigger('keyup', { key: '/' });
|
||||
|
||||
const searchInput = ndv.getters.searchInput();
|
||||
|
||||
searchInput.filter(':focus').should('exist');
|
||||
ndv.getters.pagination().find('li').should('have.length', 3);
|
||||
cy.get('.highlight').should('not.exist');
|
||||
|
||||
searchInput.type('ar');
|
||||
ndv.getters.pagination().find('li').should('have.length', 2);
|
||||
cy.get('.highlight').its('length').should('be.gt', 0);
|
||||
|
||||
searchInput.type('i');
|
||||
ndv.getters.pagination().should('not.exist');
|
||||
cy.get('.highlight').its('length').should('be.gt', 0);
|
||||
});
|
||||
|
||||
it.only('should filter input/output data separately', () => {
|
||||
workflowPage.getters.canvasNodes().eq(1).dblclick();
|
||||
cy.wait(500);
|
||||
ndv.getters.outputDataContainer().should('be.visible');
|
||||
ndv.getters.inputDataContainer().should('be.visible');
|
||||
ndv.actions.switchInputMode('Table');
|
||||
cy.document().trigger('keyup', { key: '/' });
|
||||
|
||||
ndv.getters.outputPanel().findChildByTestId('ndv-search').filter(':focus').should('not.exist');
|
||||
|
||||
let focusedInput = ndv.getters
|
||||
.inputPanel()
|
||||
.findChildByTestId('ndv-search')
|
||||
.filter(':focus')
|
||||
.should('exist');
|
||||
|
||||
const getInputPagination = () =>
|
||||
ndv.getters.inputPanel().findChildByTestId('ndv-data-pagination');
|
||||
const getInputCounter = () => ndv.getters.inputPanel().findChildByTestId('ndv-items-count');
|
||||
const getOuputPagination = () =>
|
||||
ndv.getters.outputPanel().findChildByTestId('ndv-data-pagination');
|
||||
const getOutputCounter = () => ndv.getters.outputPanel().findChildByTestId('ndv-items-count');
|
||||
|
||||
getInputPagination().find('li').should('have.length', 3);
|
||||
getInputCounter().contains('21 items').should('exist');
|
||||
getOuputPagination().find('li').should('have.length', 3);
|
||||
getOutputCounter().contains('21 items').should('exist');
|
||||
focusedInput.type('ar');
|
||||
|
||||
getInputPagination().find('li').should('have.length', 2);
|
||||
getInputCounter().should('contain', '14 of 21 items');
|
||||
getOuputPagination().find('li').should('have.length', 3);
|
||||
getOutputCounter().should('contain', '21 items');
|
||||
focusedInput.type('i');
|
||||
|
||||
getInputPagination().should('not.exist');
|
||||
getInputCounter().should('contain', '8 of 21 items');
|
||||
getOuputPagination().find('li').should('have.length', 3);
|
||||
getOutputCounter().should('contain', '21 items');
|
||||
|
||||
focusedInput.clear();
|
||||
getInputPagination().find('li').should('have.length', 3);
|
||||
getInputCounter().contains('21 items').should('exist');
|
||||
getOuputPagination().find('li').should('have.length', 3);
|
||||
getOutputCounter().contains('21 items').should('exist');
|
||||
|
||||
ndv.getters.outputDataContainer().trigger('mouseover');
|
||||
cy.document().trigger('keyup', { key: '/' });
|
||||
ndv.getters.inputPanel().findChildByTestId('ndv-search').filter(':focus').should('not.exist');
|
||||
|
||||
focusedInput = ndv.getters
|
||||
.outputPanel()
|
||||
.findChildByTestId('ndv-search')
|
||||
.filter(':focus')
|
||||
.should('exist');
|
||||
|
||||
getInputPagination().find('li').should('have.length', 3);
|
||||
getInputCounter().contains('21 items').should('exist');
|
||||
getOuputPagination().find('li').should('have.length', 3);
|
||||
getOutputCounter().contains('21 items').should('exist');
|
||||
focusedInput.type('ar');
|
||||
|
||||
getInputPagination().find('li').should('have.length', 3);
|
||||
getInputCounter().contains('21 items').should('exist');
|
||||
getOuputPagination().find('li').should('have.length', 2);
|
||||
getOutputCounter().should('contain', '14 of 21 items');
|
||||
focusedInput.type('i');
|
||||
|
||||
getInputPagination().find('li').should('have.length', 3);
|
||||
getInputCounter().contains('21 items').should('exist');
|
||||
getOuputPagination().should('not.exist');
|
||||
getOutputCounter().should('contain', '8 of 21 items');
|
||||
|
||||
focusedInput.clear();
|
||||
getInputPagination().find('li').should('have.length', 3);
|
||||
getInputCounter().contains('21 items').should('exist');
|
||||
getOuputPagination().find('li').should('have.length', 3);
|
||||
getOutputCounter().contains('21 items').should('exist');
|
||||
});
|
||||
});
|
653
cypress/fixtures/Node_IO_filter.json
Normal file
653
cypress/fixtures/Node_IO_filter.json
Normal file
|
@ -0,0 +1,653 @@
|
|||
{
|
||||
"name": "Node IO filter",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "46770685-44d1-4aad-9107-1d790cf26b50",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
840,
|
||||
180
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {}
|
||||
},
|
||||
"id": "480e3832-2ce4-4118-9f7b-a8aed6017174",
|
||||
"name": "Edit Fields",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.2,
|
||||
"position": [
|
||||
1080,
|
||||
180
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"string": [
|
||||
{
|
||||
"value1": "={{ $json.profile.name }}",
|
||||
"operation": "contains",
|
||||
"value2": "an"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "4773d460-6ed9-49e1-a688-7e480f0fbacf",
|
||||
"name": "IF",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1300,
|
||||
180
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {}
|
||||
},
|
||||
"id": "d17dffe6-e29c-4c1a-8b4c-9e374dcd70ea",
|
||||
"name": "True",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.2,
|
||||
"position": [
|
||||
1560,
|
||||
60
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {}
|
||||
},
|
||||
"id": "893d6e79-feb4-4752-a6f8-e2e5f5163787",
|
||||
"name": "False",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.2,
|
||||
"position": [
|
||||
1560,
|
||||
240
|
||||
]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"When clicking \"Execute Workflow\"": [
|
||||
{
|
||||
"json": {
|
||||
"id": "654cfa05fa51480dcb543b1a",
|
||||
"email": "reese_hahn@kidgrease.coach",
|
||||
"username": "reese94",
|
||||
"profile": {
|
||||
"name": "Reese Hahn",
|
||||
"company": "Kidgrease",
|
||||
"dob": "1994-06-18",
|
||||
"address": "3 Richmond Street, Norfolk, Delaware",
|
||||
"location": {
|
||||
"lat": 22.507436,
|
||||
"long": -50.812775
|
||||
},
|
||||
"about": "Cupidatat voluptate reprehenderit commodo mollit tempor sint id. Id exercitation id eiusmod dolore non non anim voluptate anim eu consectetur."
|
||||
},
|
||||
"apiKey": "a18592bf-1147-4b61-a70f-2ab90b60bb6e",
|
||||
"roles": [
|
||||
"guest"
|
||||
],
|
||||
"createdAt": "2010-10-04T09:57:59.240Z",
|
||||
"updatedAt": "2010-10-05T09:57:59.240Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "654cfa055bea471bc4853158",
|
||||
"email": "jeanne_boyd@hatology.gratis",
|
||||
"username": "jeanne91",
|
||||
"profile": {
|
||||
"name": "Jeanne Boyd",
|
||||
"company": "Hatology",
|
||||
"dob": "1991-02-21",
|
||||
"address": "81 Kingsway Place, Blairstown, Vermont",
|
||||
"location": {
|
||||
"lat": -57.665234,
|
||||
"long": -41.301893
|
||||
},
|
||||
"about": "Proident pariatur non consequat cupidatat Lorem nisi est consequat dolor id eiusmod id. Amet culpa ex Lorem nostrud labore laboris culpa mollit dolor culpa ut."
|
||||
},
|
||||
"apiKey": "8a6056a6-0197-4920-858d-cb26f8c8a1e2",
|
||||
"roles": [
|
||||
"owner",
|
||||
"admin"
|
||||
],
|
||||
"createdAt": "2011-11-06T09:05:41.945Z",
|
||||
"updatedAt": "2011-11-07T09:05:41.945Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "654cfa05b012921c060dc5a5",
|
||||
"email": "roslyn_underwood@portico.melbourne",
|
||||
"username": "roslyn88",
|
||||
"profile": {
|
||||
"name": "Roslyn Underwood",
|
||||
"company": "Portico",
|
||||
"dob": "1988-04-30",
|
||||
"address": "24 Schenck Street, Drytown, New Jersey",
|
||||
"location": {
|
||||
"lat": 11.797141,
|
||||
"long": 10.751804
|
||||
},
|
||||
"about": "Duis excepteur minim consequat exercitation. Laboris occaecat cupidatat aliqua consequat occaecat."
|
||||
},
|
||||
"apiKey": "72d629f3-d613-4fd0-bbfe-3f67c8ad7af2",
|
||||
"roles": [
|
||||
"member",
|
||||
"owner"
|
||||
],
|
||||
"createdAt": "2012-11-17T22:09:10.911Z",
|
||||
"updatedAt": "2012-11-18T22:09:10.911Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "654cfa05df7b35968507efe6",
|
||||
"email": "combs_hardy@acrodance.domains",
|
||||
"username": "combs91",
|
||||
"profile": {
|
||||
"name": "Combs Hardy",
|
||||
"company": "Acrodance",
|
||||
"dob": "1991-04-30",
|
||||
"address": "58 Pineapple Street, Falconaire, New Mexico",
|
||||
"location": {
|
||||
"lat": -62.922443,
|
||||
"long": -159.493799
|
||||
},
|
||||
"about": "Magna qui minim velit magna est eiusmod aliquip elit aliquip excepteur. Laborum labore do ut et ut in incididunt do elit nostrud."
|
||||
},
|
||||
"apiKey": "d9807b9e-aee9-486d-9826-4e6c166bfbe4",
|
||||
"roles": [
|
||||
"owner",
|
||||
"member"
|
||||
],
|
||||
"createdAt": "2014-04-13T13:02:09.319Z",
|
||||
"updatedAt": "2014-04-14T13:02:09.319Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "654cfa05f2d4a0508a7c59c4",
|
||||
"email": "terrell_peters@vantage.international",
|
||||
"username": "terrell94",
|
||||
"profile": {
|
||||
"name": "Terrell Peters",
|
||||
"company": "Vantage",
|
||||
"dob": "1994-01-31",
|
||||
"address": "10 Lafayette Walk, Vincent, Virginia",
|
||||
"location": {
|
||||
"lat": -62.267913,
|
||||
"long": 29.682121
|
||||
},
|
||||
"about": "Eiusmod fugiat nulla ea tempor incididunt nulla nulla consectetur officia incididunt proident sint. Sunt duis non excepteur non."
|
||||
},
|
||||
"apiKey": "20b96df1-d882-4dea-a505-84d7ff296a6e",
|
||||
"roles": [
|
||||
"admin",
|
||||
"guest"
|
||||
],
|
||||
"createdAt": "2010-12-09T08:24:56.517Z",
|
||||
"updatedAt": "2010-12-10T08:24:56.517Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "654cfa0599fbabf3a05c7b14",
|
||||
"email": "shari_winters@powernet.supply",
|
||||
"username": "shari93",
|
||||
"profile": {
|
||||
"name": "Shari Winters",
|
||||
"company": "Powernet",
|
||||
"dob": "1993-03-10",
|
||||
"address": "89 Aviation Road, Leyner, Indiana",
|
||||
"location": {
|
||||
"lat": 40.404704,
|
||||
"long": -141.216235
|
||||
},
|
||||
"about": "Occaecat sit laboris elit laboris do anim culpa dolore exercitation enim. Non veniam sint exercitation irure."
|
||||
},
|
||||
"apiKey": "2b869ce9-3431-4edb-944d-9d9336b1eb4a",
|
||||
"roles": [
|
||||
"guest",
|
||||
"admin"
|
||||
],
|
||||
"createdAt": "2014-10-15T15:56:55.873Z",
|
||||
"updatedAt": "2014-10-16T15:56:55.873Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "654cfa050df18b4798ec95be",
|
||||
"email": "rena_beasley@bitrex.ma",
|
||||
"username": "rena90",
|
||||
"profile": {
|
||||
"name": "Rena Beasley",
|
||||
"company": "Bitrex",
|
||||
"dob": "1990-01-09",
|
||||
"address": "78 Forbell Street, Homeland, Maine",
|
||||
"location": {
|
||||
"lat": 46.047548,
|
||||
"long": 4.128049
|
||||
},
|
||||
"about": "Lorem aliqua veniam duis ut cillum ad sunt mollit incididunt elit. Ipsum incididunt et magna incididunt quis duis amet duis occaecat laborum nulla et commodo nisi."
|
||||
},
|
||||
"apiKey": "17e350f8-1020-4344-bbd7-ceb62cd44edb",
|
||||
"roles": [
|
||||
"member",
|
||||
"owner"
|
||||
],
|
||||
"createdAt": "2010-04-22T13:35:24.838Z",
|
||||
"updatedAt": "2010-04-23T13:35:24.838Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "654cfa0595243d2b7b1ea22a",
|
||||
"email": "sally_gentry@eventex.maif",
|
||||
"username": "sally93",
|
||||
"profile": {
|
||||
"name": "Sally Gentry",
|
||||
"company": "Eventex",
|
||||
"dob": "1993-04-03",
|
||||
"address": "54 Plaza Street, Greenbackville, North Carolina",
|
||||
"location": {
|
||||
"lat": -20.529121,
|
||||
"long": 73.533118
|
||||
},
|
||||
"about": "Laborum sit exercitation sint laborum. Fugiat sit ipsum ullamco sint do dolore in sunt incididunt adipisicing magna ullamco aute."
|
||||
},
|
||||
"apiKey": "746b6ab3-c63f-44df-bb99-9de48f8e43c4",
|
||||
"roles": [
|
||||
"owner",
|
||||
"guest"
|
||||
],
|
||||
"createdAt": "2011-09-18T13:18:49.655Z",
|
||||
"updatedAt": "2011-09-19T13:18:49.655Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "654cfa05cdea66c87bb01439",
|
||||
"email": "battle_duran@jasper.property",
|
||||
"username": "battle88",
|
||||
"profile": {
|
||||
"name": "Battle Duran",
|
||||
"company": "Jasper",
|
||||
"dob": "1988-11-04",
|
||||
"address": "34 Amherst Street, Corriganville, Nevada",
|
||||
"location": {
|
||||
"lat": 74.391489,
|
||||
"long": -98.421464
|
||||
},
|
||||
"about": "Nostrud occaecat laborum aliquip sint est minim id aliquip adipisicing dolor. Aute velit amet officia anim sint anim aliquip."
|
||||
},
|
||||
"apiKey": "b22a3ddd-d540-4df0-9ce5-e837bc6a6a10",
|
||||
"roles": [
|
||||
"member"
|
||||
],
|
||||
"createdAt": "2012-08-31T19:14:37.463Z",
|
||||
"updatedAt": "2012-09-01T19:14:37.463Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "654cfa05e9c13e25d41d4135",
|
||||
"email": "petty_moore@neurocell.shriram",
|
||||
"username": "petty91",
|
||||
"profile": {
|
||||
"name": "Petty Moore",
|
||||
"company": "Neurocell",
|
||||
"dob": "1991-03-10",
|
||||
"address": "78 Interborough Parkway, Grill, Texas",
|
||||
"location": {
|
||||
"lat": -79.817761,
|
||||
"long": -36.728201
|
||||
},
|
||||
"about": "Dolor occaecat anim est Lorem culpa fugiat id aliqua sint. Sit nisi do exercitation do voluptate exercitation in."
|
||||
},
|
||||
"apiKey": "4b341cfb-a83c-4f2a-9f4d-11cd747b8783",
|
||||
"roles": [
|
||||
"admin"
|
||||
],
|
||||
"createdAt": "2012-01-02T21:28:22.431Z",
|
||||
"updatedAt": "2012-01-03T21:28:22.431Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "654cfa052890c7b4d510d3d4",
|
||||
"email": "matilda_kelley@senmei.in",
|
||||
"username": "matilda93",
|
||||
"profile": {
|
||||
"name": "Matilda Kelley",
|
||||
"company": "Senmei",
|
||||
"dob": "1993-02-04",
|
||||
"address": "29 Stuart Street, Henrietta, New York",
|
||||
"location": {
|
||||
"lat": 40.788206,
|
||||
"long": -135.821558
|
||||
},
|
||||
"about": "Dolor veniam ex ullamco deserunt reprehenderit nostrud sunt culpa cupidatat qui labore deserunt. In ad anim laboris amet labore duis consequat nostrud eiusmod."
|
||||
},
|
||||
"apiKey": "dcf40383-a00a-43ef-8bd0-4af7e70413bd",
|
||||
"roles": [
|
||||
"owner",
|
||||
"guest"
|
||||
],
|
||||
"createdAt": "2014-03-28T22:07:39.636Z",
|
||||
"updatedAt": "2014-03-29T22:07:39.636Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "654cfa05af129db469473bf1",
|
||||
"email": "savannah_hardin@exoblue.kn",
|
||||
"username": "savannah89",
|
||||
"profile": {
|
||||
"name": "Savannah Hardin",
|
||||
"company": "Exoblue",
|
||||
"dob": "1989-07-01",
|
||||
"address": "44 Navy Walk, Fresno, Kentucky",
|
||||
"location": {
|
||||
"lat": 75.679679,
|
||||
"long": -58.534947
|
||||
},
|
||||
"about": "Id eiusmod eu elit consequat quis anim veniam officia anim ipsum. Sunt ex sit ipsum id est eu."
|
||||
},
|
||||
"apiKey": "98d6abb7-e4aa-4b3b-8958-ff3c4d672f1d",
|
||||
"roles": [
|
||||
"guest",
|
||||
"member"
|
||||
],
|
||||
"createdAt": "2011-04-15T00:55:02.325Z",
|
||||
"updatedAt": "2011-04-16T00:55:02.325Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "654cfa055dfa731b01573a67",
|
||||
"email": "abbott_gallegos@katakana.dad",
|
||||
"username": "abbott91",
|
||||
"profile": {
|
||||
"name": "Abbott Gallegos",
|
||||
"company": "Katakana",
|
||||
"dob": "1991-03-04",
|
||||
"address": "85 Indiana Place, Forestburg, Michigan",
|
||||
"location": {
|
||||
"lat": -5.417414,
|
||||
"long": -4.557904
|
||||
},
|
||||
"about": "Adipisicing amet ullamco aliquip velit nostrud qui non pariatur Lorem. Culpa ut deserunt esse quis magna."
|
||||
},
|
||||
"apiKey": "3cf92c24-6193-4cc9-85fc-78e4ad9d6e13",
|
||||
"roles": [
|
||||
"guest",
|
||||
"owner"
|
||||
],
|
||||
"createdAt": "2011-06-01T16:38:39.316Z",
|
||||
"updatedAt": "2011-06-02T16:38:39.316Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "654cfa05386de2e6d75c1694",
|
||||
"email": "short_brennan@hyplex.tc",
|
||||
"username": "short92",
|
||||
"profile": {
|
||||
"name": "Short Brennan",
|
||||
"company": "Hyplex",
|
||||
"dob": "1992-04-19",
|
||||
"address": "21 Irving Place, Hinsdale, Northern Mariana Islands",
|
||||
"location": {
|
||||
"lat": 57.340225,
|
||||
"long": -7.021582
|
||||
},
|
||||
"about": "Mollit dolor dolore deserunt anim minim adipisicing eiusmod velit tempor id veniam cupidatat. Magna veniam consequat incididunt ut quis culpa excepteur tempor eiusmod consectetur excepteur."
|
||||
},
|
||||
"apiKey": "07bf533d-4a31-4e78-9d6e-d46160479069",
|
||||
"roles": [
|
||||
"admin",
|
||||
"member"
|
||||
],
|
||||
"createdAt": "2014-03-10T19:25:02.217Z",
|
||||
"updatedAt": "2014-03-11T19:25:02.217Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "654cfa05fd2a878d43bb45cd",
|
||||
"email": "bowers_cooke@iplax.ci",
|
||||
"username": "bowers92",
|
||||
"profile": {
|
||||
"name": "Bowers Cooke",
|
||||
"company": "Iplax",
|
||||
"dob": "1992-07-05",
|
||||
"address": "83 Greenpoint Avenue, Marion, Georgia",
|
||||
"location": {
|
||||
"lat": 64.261022,
|
||||
"long": -58.493714
|
||||
},
|
||||
"about": "Deserunt ipsum fugiat tempor sunt eu ea laboris ad magna ex laborum laboris. Ullamco nostrud qui exercitation aute consectetur irure."
|
||||
},
|
||||
"apiKey": "a3ecc58b-f292-4de1-b6e5-014345a76a7a",
|
||||
"roles": [
|
||||
"member",
|
||||
"owner"
|
||||
],
|
||||
"createdAt": "2010-06-20T16:34:56.467Z",
|
||||
"updatedAt": "2010-06-21T16:34:56.467Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "654cfa05a6de547367990f9c",
|
||||
"email": "tara_rutledge@escenta.lc",
|
||||
"username": "tara90",
|
||||
"profile": {
|
||||
"name": "Tara Rutledge",
|
||||
"company": "Escenta",
|
||||
"dob": "1990-08-11",
|
||||
"address": "25 Butler Place, Frierson, Missouri",
|
||||
"location": {
|
||||
"lat": -32.176783,
|
||||
"long": 67.345415
|
||||
},
|
||||
"about": "Aute sunt laborum anim ex non pariatur nisi minim tempor adipisicing. Excepteur irure non amet eiusmod et excepteur."
|
||||
},
|
||||
"apiKey": "22da9647-a7b7-4815-91bb-d5101fc90e55",
|
||||
"roles": [
|
||||
"member"
|
||||
],
|
||||
"createdAt": "2013-09-06T21:41:53.287Z",
|
||||
"updatedAt": "2013-09-07T21:41:53.287Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "654cfa053778601ad57f22cd",
|
||||
"email": "elva_chapman@bytrex.gg",
|
||||
"username": "elva90",
|
||||
"profile": {
|
||||
"name": "Elva Chapman",
|
||||
"company": "Bytrex",
|
||||
"dob": "1990-05-31",
|
||||
"address": "4 Royce Place, Advance, New Hampshire",
|
||||
"location": {
|
||||
"lat": -28.393464,
|
||||
"long": -28.622091
|
||||
},
|
||||
"about": "Est sit deserunt Lorem amet voluptate elit reprehenderit occaecat est eiusmod eu reprehenderit laborum. Pariatur magna occaecat et excepteur est excepteur consectetur ad nulla."
|
||||
},
|
||||
"apiKey": "4d242fa4-ac69-42f1-8f12-ec19d9c6d632",
|
||||
"roles": [
|
||||
"owner",
|
||||
"admin"
|
||||
],
|
||||
"createdAt": "2011-04-05T04:04:31.524Z",
|
||||
"updatedAt": "2011-04-06T04:04:31.524Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "654cfa054c6abbc57efcb100",
|
||||
"email": "pitts_meyer@unisure.tui",
|
||||
"username": "pitts93",
|
||||
"profile": {
|
||||
"name": "Pitts Meyer",
|
||||
"company": "Unisure",
|
||||
"dob": "1993-06-12",
|
||||
"address": "47 Columbus Place, Cade, Alaska",
|
||||
"location": {
|
||||
"lat": 56.723675,
|
||||
"long": 158.093389
|
||||
},
|
||||
"about": "Non ea pariatur excepteur nostrud elit quis qui. Dolore aute velit ipsum officia ea pariatur incididunt non elit tempor duis consequat."
|
||||
},
|
||||
"apiKey": "82a88344-d289-447c-81b5-1ae10cd1994b",
|
||||
"roles": [
|
||||
"guest",
|
||||
"admin"
|
||||
],
|
||||
"createdAt": "2014-05-15T06:38:59.269Z",
|
||||
"updatedAt": "2014-05-16T06:38:59.269Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "654cfa0527e7ce14e421d9cd",
|
||||
"email": "delia_figueroa@overplex.um",
|
||||
"username": "delia89",
|
||||
"profile": {
|
||||
"name": "Delia Figueroa",
|
||||
"company": "Overplex",
|
||||
"dob": "1989-04-22",
|
||||
"address": "12 Nova Court, Taft, Ohio",
|
||||
"location": {
|
||||
"lat": -32.990583,
|
||||
"long": -4.598863
|
||||
},
|
||||
"about": "Cupidatat fugiat veniam eu proident excepteur deserunt ad esse fugiat deserunt. Non velit cillum velit veniam ex minim eiusmod tempor excepteur voluptate adipisicing nostrud."
|
||||
},
|
||||
"apiKey": "b3a7747b-24a0-4039-8a21-56e83441a660",
|
||||
"roles": [
|
||||
"admin",
|
||||
"guest"
|
||||
],
|
||||
"createdAt": "2014-09-20T03:40:10.190Z",
|
||||
"updatedAt": "2014-09-21T03:40:10.190Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "654cfa05cf60000cbca6dca4",
|
||||
"email": "kristina_fulton@portaline.engineer",
|
||||
"username": "kristina88",
|
||||
"profile": {
|
||||
"name": "Kristina Fulton",
|
||||
"company": "Portaline",
|
||||
"dob": "1988-07-25",
|
||||
"address": "50 Laurel Avenue, Greenwich, Palau",
|
||||
"location": {
|
||||
"lat": 44.118984,
|
||||
"long": 41.518949
|
||||
},
|
||||
"about": "Id incididunt officia exercitation ipsum id cillum consectetur. Veniam enim voluptate ut proident ex."
|
||||
},
|
||||
"apiKey": "c106dbf0-bfc0-461d-b1d7-1840fe8e1cbc",
|
||||
"roles": [
|
||||
"admin",
|
||||
"member"
|
||||
],
|
||||
"createdAt": "2010-04-10T08:06:27.028Z",
|
||||
"updatedAt": "2010-04-11T08:06:27.028Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "654cfa0501fe5691d620f570",
|
||||
"email": "gould_noel@gonkle.gmx",
|
||||
"username": "gould91",
|
||||
"profile": {
|
||||
"name": "Gould Noel",
|
||||
"company": "Gonkle",
|
||||
"dob": "1991-10-08",
|
||||
"address": "33 Crooke Avenue, Idamay, Oklahoma",
|
||||
"location": {
|
||||
"lat": -11.398731,
|
||||
"long": 34.706948
|
||||
},
|
||||
"about": "Veniam esse tempor aute quis mollit consequat Lorem. Nostrud ea dolore laboris Lorem elit est do nisi Lorem minim reprehenderit culpa."
|
||||
},
|
||||
"apiKey": "1089783d-32ae-4102-8ac5-1e7f6cebe3c1",
|
||||
"roles": [
|
||||
"guest",
|
||||
"admin"
|
||||
],
|
||||
"createdAt": "2011-12-30T20:24:19.620Z",
|
||||
"updatedAt": "2011-12-31T20:24:19.620Z"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Edit Fields",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Edit Fields": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "IF",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"IF": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "True",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "False",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"versionId": "9812dda2-cc1b-4458-97d8-21ccb18c90d1",
|
||||
"id": "WNq486x7DpV1MPRH",
|
||||
"meta": {
|
||||
"instanceId": "8a47b83b4479b11330fdf21ccc96d4a8117035a968612e452b4c87bfd09c16c7"
|
||||
},
|
||||
"tags": []
|
||||
}
|
|
@ -78,6 +78,8 @@ export class NDV extends BasePage {
|
|||
cy.getByTestId('columns-parameter-input-options-container'),
|
||||
resourceMapperRemoveAllFieldsOption: () => cy.getByTestId('action-removeAllFields'),
|
||||
sqlEditorContainer: () => cy.getByTestId('sql-editor-container'),
|
||||
searchInput: () => cy.getByTestId('ndv-search'),
|
||||
pagination: () => cy.getByTestId('ndv-data-pagination'),
|
||||
};
|
||||
|
||||
actions = {
|
||||
|
|
|
@ -13,12 +13,15 @@
|
|||
:mappingEnabled="isMappingEnabled"
|
||||
:distanceFromActive="currentNodeDepth"
|
||||
:isProductionExecutionPreview="isProductionExecutionPreview"
|
||||
:isPaneActive="isPaneActive"
|
||||
@activatePane="activatePane"
|
||||
paneType="input"
|
||||
@itemHover="$emit('itemHover', $event)"
|
||||
@linkRun="onLinkRun"
|
||||
@unlinkRun="onUnlinkRun"
|
||||
@runChange="onRunIndexChange"
|
||||
@tableMounted="$emit('tableMounted', $event)"
|
||||
@search="$emit('search', $event)"
|
||||
data-test-id="ndv-input-panel"
|
||||
>
|
||||
<template #header>
|
||||
|
@ -209,6 +212,10 @@ export default defineComponent({
|
|||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isPaneActive: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -455,6 +462,9 @@ export default defineComponent({
|
|||
}
|
||||
return truncated;
|
||||
},
|
||||
activatePane() {
|
||||
this.$emit('activatePane');
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
inputMode: {
|
||||
|
|
|
@ -65,6 +65,8 @@
|
|||
:sessionId="sessionId"
|
||||
:readOnly="readOnly || hasForeignCredential"
|
||||
:isProductionExecutionPreview="isProductionExecutionPreview"
|
||||
:isPaneActive="isInputPaneActive"
|
||||
@activatePane="activateInputPane"
|
||||
@linkRun="onLinkRunToInput"
|
||||
@unlinkRun="() => onUnlinkRun('input')"
|
||||
@runChange="onRunInputIndexChange"
|
||||
|
@ -73,6 +75,7 @@
|
|||
@execute="onNodeExecute"
|
||||
@tableMounted="onInputTableMounted"
|
||||
@itemHover="onInputItemHover"
|
||||
@search="onSearch"
|
||||
/>
|
||||
</template>
|
||||
<template #output>
|
||||
|
@ -85,12 +88,15 @@
|
|||
:isReadOnly="readOnly || hasForeignCredential"
|
||||
:blockUI="blockUi && isTriggerNode && !isExecutableTriggerNode"
|
||||
:isProductionExecutionPreview="isProductionExecutionPreview"
|
||||
:isPaneActive="isOutputPaneActive"
|
||||
@activatePane="activateOutputPane"
|
||||
@linkRun="onLinkRunToOutput"
|
||||
@unlinkRun="() => onUnlinkRun('output')"
|
||||
@runChange="onRunOutputIndexChange"
|
||||
@openSettings="openSettings"
|
||||
@tableMounted="onOutputTableMounted"
|
||||
@itemHover="onOutputItemHover"
|
||||
@search="onSearch"
|
||||
/>
|
||||
</template>
|
||||
<template #main>
|
||||
|
@ -211,6 +217,9 @@ export default defineComponent({
|
|||
pinDataDiscoveryTooltipVisible: false,
|
||||
avgInputRowHeight: 0,
|
||||
avgOutputRowHeight: 0,
|
||||
isInputPaneActive: false,
|
||||
isOutputPaneActive: false,
|
||||
isPairedItemHoveringEnabled: true,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
|
@ -516,10 +525,7 @@ export default defineComponent({
|
|||
}
|
||||
},
|
||||
onInputItemHover(e: { itemIndex: number; outputIndex: number } | null) {
|
||||
if (!this.inputNodeName) {
|
||||
return;
|
||||
}
|
||||
if (e === null) {
|
||||
if (e === null || !this.inputNodeName || !this.isPairedItemHoveringEnabled) {
|
||||
this.ndvStore.setHoveringItem(null);
|
||||
return;
|
||||
}
|
||||
|
@ -533,7 +539,7 @@ export default defineComponent({
|
|||
this.ndvStore.setHoveringItem(item);
|
||||
},
|
||||
onOutputItemHover(e: { itemIndex: number; outputIndex: number } | null) {
|
||||
if (e === null || !this.activeNode) {
|
||||
if (e === null || !this.activeNode || !this.isPairedItemHoveringEnabled) {
|
||||
this.ndvStore.setHoveringItem(null);
|
||||
return;
|
||||
}
|
||||
|
@ -717,6 +723,17 @@ export default defineComponent({
|
|||
onStopExecution() {
|
||||
this.$emit('stopExecution');
|
||||
},
|
||||
activateInputPane() {
|
||||
this.isInputPaneActive = true;
|
||||
this.isOutputPaneActive = false;
|
||||
},
|
||||
activateOutputPane() {
|
||||
this.isInputPaneActive = false;
|
||||
this.isOutputPaneActive = true;
|
||||
},
|
||||
onSearch(search: string) {
|
||||
this.isPairedItemHoveringEnabled = !search;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -11,12 +11,15 @@
|
|||
:sessionId="sessionId"
|
||||
:blockUI="blockUI"
|
||||
:isProductionExecutionPreview="isProductionExecutionPreview"
|
||||
:isPaneActive="isPaneActive"
|
||||
@activatePane="activatePane"
|
||||
paneType="output"
|
||||
@runChange="onRunIndexChange"
|
||||
@linkRun="onLinkRun"
|
||||
@unlinkRun="onUnlinkRun"
|
||||
@tableMounted="$emit('tableMounted', $event)"
|
||||
@itemHover="$emit('itemHover', $event)"
|
||||
@search="$emit('search', $event)"
|
||||
ref="runData"
|
||||
:data-output-type="outputMode"
|
||||
>
|
||||
|
@ -166,6 +169,10 @@ export default defineComponent({
|
|||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isPaneActive: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useNodeTypesStore, useNDVStore, useUIStore, useWorkflowsStore),
|
||||
|
@ -320,6 +327,9 @@ export default defineComponent({
|
|||
ndvEventBus.emit('setPositionByName', 'initial');
|
||||
}
|
||||
},
|
||||
activatePane() {
|
||||
this.$emit('activatePane');
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div :class="['run-data', $style.container]">
|
||||
<div :class="['run-data', $style.container]" @mouseover="activatePane">
|
||||
<n8n-callout
|
||||
v-if="canPinData && hasPinData && !editMode.enabled && !isProductionExecutionPreview"
|
||||
theme="secondary"
|
||||
|
@ -49,9 +49,7 @@
|
|||
>
|
||||
<n8n-radio-buttons
|
||||
v-show="
|
||||
hasNodeRun &&
|
||||
((jsonData && jsonData.length > 0) || (binaryData && binaryData.length > 0)) &&
|
||||
!editMode.enabled
|
||||
hasNodeRun && (inputData.length || binaryData.length || search) && !editMode.enabled
|
||||
"
|
||||
:modelValue="displayMode"
|
||||
:options="buttons"
|
||||
|
@ -72,7 +70,7 @@
|
|||
/>
|
||||
<n8n-tooltip
|
||||
placement="bottom-end"
|
||||
v-if="canPinData && jsonData && jsonData.length > 0"
|
||||
v-if="canPinData && rawInputData.length"
|
||||
v-show="!editMode.enabled"
|
||||
:visible="
|
||||
isControlledPinDataTooltip
|
||||
|
@ -135,37 +133,44 @@
|
|||
v-show="!editMode.enabled"
|
||||
data-test-id="run-selector"
|
||||
>
|
||||
<n8n-select
|
||||
size="small"
|
||||
:modelValue="runIndex"
|
||||
@update:modelValue="onRunIndexChange"
|
||||
@click.stop
|
||||
teleported
|
||||
>
|
||||
<template #prepend>{{ $locale.baseText('ndv.output.run') }}</template>
|
||||
<n8n-option
|
||||
v-for="option in maxRunIndex + 1"
|
||||
:label="getRunLabel(option)"
|
||||
:value="option - 1"
|
||||
:key="option"
|
||||
></n8n-option>
|
||||
</n8n-select>
|
||||
|
||||
<n8n-tooltip placement="right" v-if="canLinkRuns">
|
||||
<template #content>
|
||||
{{ $locale.baseText(linkedRuns ? 'runData.unlinking.hint' : 'runData.linking.hint') }}
|
||||
</template>
|
||||
<n8n-icon-button
|
||||
class="linkRun"
|
||||
:icon="linkedRuns ? 'unlink' : 'link'"
|
||||
text
|
||||
type="tertiary"
|
||||
<div :class="$style.runSelectorWrapper">
|
||||
<n8n-select
|
||||
size="small"
|
||||
@click="toggleLinkRuns"
|
||||
/>
|
||||
</n8n-tooltip>
|
||||
|
||||
<slot name="run-info"></slot>
|
||||
:modelValue="runIndex"
|
||||
@update:modelValue="onRunIndexChange"
|
||||
@click.stop
|
||||
teleported
|
||||
>
|
||||
<template #prepend>{{ $locale.baseText('ndv.output.run') }}</template>
|
||||
<n8n-option
|
||||
v-for="option in maxRunIndex + 1"
|
||||
:label="getRunLabel(option)"
|
||||
:value="option - 1"
|
||||
:key="option"
|
||||
></n8n-option>
|
||||
</n8n-select>
|
||||
<n8n-tooltip placement="right" v-if="canLinkRuns">
|
||||
<template #content>
|
||||
{{ $locale.baseText(linkedRuns ? 'runData.unlinking.hint' : 'runData.linking.hint') }}
|
||||
</template>
|
||||
<n8n-icon-button
|
||||
class="linkRun"
|
||||
:icon="linkedRuns ? 'unlink' : 'link'"
|
||||
text
|
||||
type="tertiary"
|
||||
size="small"
|
||||
@click="toggleLinkRuns"
|
||||
/>
|
||||
</n8n-tooltip>
|
||||
<slot name="run-info"></slot>
|
||||
</div>
|
||||
<run-data-search
|
||||
v-if="showIOSearch"
|
||||
v-model="search"
|
||||
:paneType="paneType"
|
||||
:isAreaActive="isPaneActive"
|
||||
@focus="activatePane"
|
||||
/>
|
||||
</div>
|
||||
<slot name="before-data" />
|
||||
|
||||
|
@ -179,18 +184,48 @@
|
|||
:options="branches"
|
||||
@update:modelValue="onBranchChange"
|
||||
/>
|
||||
<run-data-search
|
||||
v-if="showIOSearch"
|
||||
v-model="search"
|
||||
:paneType="paneType"
|
||||
:isAreaActive="isPaneActive"
|
||||
@focus="activatePane"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="
|
||||
hasNodeRun && dataCount > 0 && maxRunIndex === 0 && !isArtificialRecoveredEventItem
|
||||
hasNodeRun &&
|
||||
((dataCount > 0 && maxRunIndex === 0) || search) &&
|
||||
!isArtificialRecoveredEventItem
|
||||
"
|
||||
v-show="!editMode.enabled"
|
||||
:class="$style.itemsCount"
|
||||
data-test-id="ndv-items-count"
|
||||
>
|
||||
<n8n-text>
|
||||
{{ dataCount }} {{ $locale.baseText('ndv.output.items', { adjustToNumber: dataCount }) }}
|
||||
<n8n-text v-if="search">
|
||||
{{
|
||||
$locale.baseText('ndv.search.items', {
|
||||
adjustToNumber: unfilteredDataCount,
|
||||
interpolate: { matched: dataCount, total: unfilteredDataCount },
|
||||
})
|
||||
}}
|
||||
</n8n-text>
|
||||
<n8n-text v-else>
|
||||
{{
|
||||
$locale.baseText('ndv.output.items', {
|
||||
adjustToNumber: dataCount,
|
||||
interpolate: { count: dataCount },
|
||||
})
|
||||
}}
|
||||
</n8n-text>
|
||||
<run-data-search
|
||||
v-if="showIOSearch"
|
||||
v-model="search"
|
||||
:paneType="paneType"
|
||||
:isAreaActive="isPaneActive"
|
||||
@focus="activatePane"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div :class="$style.dataContainer" ref="dataContainer" data-test-id="ndv-data-container">
|
||||
|
@ -258,15 +293,31 @@
|
|||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="hasNodeRun && jsonData && jsonData.length === 0 && branches.length > 1"
|
||||
v-else-if="
|
||||
hasNodeRun && (!unfilteredDataCount || (search && !dataCount)) && branches.length > 1
|
||||
"
|
||||
:class="$style.center"
|
||||
>
|
||||
<n8n-text>
|
||||
<div v-if="search">
|
||||
<n8n-text tag="h3" size="large">{{
|
||||
$locale.baseText('ndv.search.noMatch.title')
|
||||
}}</n8n-text>
|
||||
<n8n-text>
|
||||
<i18n-t keypath="ndv.search.noMatch.description" tag="span">
|
||||
<template #link>
|
||||
<a href="#" @click="onSearchClear">
|
||||
{{ $locale.baseText('ndv.search.noMatch.description.link') }}
|
||||
</a>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</n8n-text>
|
||||
</div>
|
||||
<n8n-text v-else>
|
||||
{{ noDataInBranchMessage }}
|
||||
</n8n-text>
|
||||
</div>
|
||||
|
||||
<div v-else-if="hasNodeRun && jsonData && jsonData.length === 0" :class="$style.center">
|
||||
<div v-else-if="hasNodeRun && !inputData.length && !search" :class="$style.center">
|
||||
<slot name="no-output-data">xxx</slot>
|
||||
</div>
|
||||
|
||||
|
@ -303,7 +354,7 @@
|
|||
hasNodeRun &&
|
||||
displayMode === 'table' &&
|
||||
binaryData.length > 0 &&
|
||||
jsonData.length === 1 &&
|
||||
inputData.length === 1 &&
|
||||
Object.keys(jsonData[0] || {}).length === 0
|
||||
"
|
||||
:class="$style.center"
|
||||
|
@ -316,6 +367,21 @@
|
|||
</n8n-text>
|
||||
</div>
|
||||
|
||||
<div v-else-if="showIoSearchNoMatchContent" :class="$style.center">
|
||||
<n8n-text tag="h3" size="large">{{
|
||||
$locale.baseText('ndv.search.noMatch.title')
|
||||
}}</n8n-text>
|
||||
<n8n-text>
|
||||
<i18n-t keypath="ndv.search.noMatch.description" tag="span">
|
||||
<template #link>
|
||||
<a href="#" @click="onSearchClear">
|
||||
{{ $locale.baseText('ndv.search.noMatch.description.link') }}
|
||||
</a>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</n8n-text>
|
||||
</div>
|
||||
|
||||
<Suspense v-else-if="hasNodeRun && displayMode === 'table'">
|
||||
<run-data-table
|
||||
:node="node"
|
||||
|
@ -325,7 +391,8 @@
|
|||
:runIndex="runIndex"
|
||||
:pageOffset="currentPageOffset"
|
||||
:totalRuns="maxRunIndex"
|
||||
:hasDefaultHoverState="paneType === 'input'"
|
||||
:hasDefaultHoverState="paneType === 'input' && !search"
|
||||
:search="search"
|
||||
@mounted="$emit('tableMounted', $event)"
|
||||
@activeRowChanged="onItemHover"
|
||||
@displayModeChange="onDisplayModeChange"
|
||||
|
@ -343,6 +410,7 @@
|
|||
:distanceFromActive="distanceFromActive"
|
||||
:runIndex="runIndex"
|
||||
:totalRuns="maxRunIndex"
|
||||
:search="search"
|
||||
/>
|
||||
</Suspense>
|
||||
|
||||
|
@ -359,6 +427,7 @@
|
|||
:paneType="paneType"
|
||||
:runIndex="runIndex"
|
||||
:totalRuns="maxRunIndex"
|
||||
:search="search"
|
||||
/>
|
||||
</Suspense>
|
||||
|
||||
|
@ -461,6 +530,7 @@
|
|||
!isArtificialRecoveredEventItem
|
||||
"
|
||||
v-show="!editMode.enabled"
|
||||
data-test-id="ndv-data-pagination"
|
||||
>
|
||||
<el-pagination
|
||||
background
|
||||
|
@ -542,7 +612,7 @@ import { pinData } from '@/mixins/pinData';
|
|||
import type { PinDataSource } from '@/mixins/pinData';
|
||||
import CodeNodeEditor from '@/components/CodeNodeEditor/CodeNodeEditor.vue';
|
||||
import { dataPinningEventBus } from '@/event-bus';
|
||||
import { clearJsonKey, executionDataToJson, isEmpty } from '@/utils';
|
||||
import { clearJsonKey, executionDataToJson, isEmpty, searchInObject } from '@/utils';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { useNDVStore } from '@/stores/ndv.store';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
|
@ -553,6 +623,7 @@ const RunDataTable = defineAsyncComponent(async () => import('@/components/RunDa
|
|||
const RunDataJson = defineAsyncComponent(async () => import('@/components/RunDataJson.vue'));
|
||||
const RunDataSchema = defineAsyncComponent(async () => import('@/components/RunDataSchema.vue'));
|
||||
const RunDataHtml = defineAsyncComponent(async () => import('@/components/RunDataHtml.vue'));
|
||||
const RunDataSearch = defineAsyncComponent(async () => import('@/components/RunDataSearch.vue'));
|
||||
|
||||
export type EnterEditModeArgs = {
|
||||
origin: 'editIconButton' | 'insertTestDataLink';
|
||||
|
@ -569,6 +640,7 @@ export default defineComponent({
|
|||
RunDataJson,
|
||||
RunDataSchema,
|
||||
RunDataHtml,
|
||||
RunDataSearch,
|
||||
},
|
||||
props: {
|
||||
nodeUi: {
|
||||
|
@ -619,6 +691,10 @@ export default defineComponent({
|
|||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isPaneActive: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
|
@ -643,6 +719,7 @@ export default defineComponent({
|
|||
|
||||
pinDataDiscoveryTooltipVisible: false,
|
||||
isControlledPinDataTooltip: false,
|
||||
search: '',
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
|
@ -656,7 +733,10 @@ export default defineComponent({
|
|||
branchIndex: this.currentOutputIndex,
|
||||
});
|
||||
|
||||
if (this.paneType === 'output') this.setDisplayMode();
|
||||
if (this.paneType === 'output') {
|
||||
this.setDisplayMode();
|
||||
this.activatePane();
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.hidePinDataDiscoveryTooltip();
|
||||
|
@ -777,6 +857,9 @@ export default defineComponent({
|
|||
dataCount(): number {
|
||||
return this.getDataCount(this.runIndex, this.currentOutputIndex);
|
||||
},
|
||||
unfilteredDataCount(): number {
|
||||
return this.pinData ? this.pinData.length : this.rawInputData.length;
|
||||
},
|
||||
dataSizeInMB(): string {
|
||||
return (this.dataSize / 1024 / 1000).toLocaleString();
|
||||
},
|
||||
|
@ -828,7 +911,8 @@ export default defineComponent({
|
|||
return this.getRawInputData(this.runIndex, this.currentOutputIndex, this.connectionType);
|
||||
},
|
||||
inputData(): INodeExecutionData[] {
|
||||
return this.getPinDataOrLiveData(this.rawInputData);
|
||||
const pinOrLiveData = this.getPinDataOrLiveData(this.rawInputData);
|
||||
return this.getFilteredData(pinOrLiveData);
|
||||
},
|
||||
inputDataPage(): INodeExecutionData[] {
|
||||
const offset = this.pageSize * (this.currentPage - 1);
|
||||
|
@ -866,8 +950,17 @@ export default defineComponent({
|
|||
if (this.overrideOutputs && !this.overrideOutputs.includes(i)) {
|
||||
continue;
|
||||
}
|
||||
const totalItemsCount = this.getRawInputData(this.runIndex, i).length;
|
||||
const itemsCount = this.getDataCount(this.runIndex, i);
|
||||
const items = this.$locale.baseText('ndv.output.items', { adjustToNumber: itemsCount });
|
||||
const items = this.search
|
||||
? this.$locale.baseText('ndv.search.items', {
|
||||
adjustToNumber: totalItemsCount,
|
||||
interpolate: { matched: itemsCount, total: totalItemsCount },
|
||||
})
|
||||
: this.$locale.baseText('ndv.output.items', {
|
||||
adjustToNumber: itemsCount,
|
||||
interpolate: { count: itemsCount },
|
||||
});
|
||||
let outputName = this.getOutputName(i);
|
||||
|
||||
if (`${outputName}` === `${i}`) {
|
||||
|
@ -881,7 +974,10 @@ export default defineComponent({
|
|||
outputName = capitalize(`${this.getOutputName(i)}${appendBranchWord}`);
|
||||
}
|
||||
branches.push({
|
||||
label: itemsCount ? `${outputName} (${itemsCount} ${items})` : outputName,
|
||||
label:
|
||||
(this.search && itemsCount) || totalItemsCount
|
||||
? `${outputName} (${items})`
|
||||
: outputName,
|
||||
value: i,
|
||||
});
|
||||
}
|
||||
|
@ -901,6 +997,12 @@ export default defineComponent({
|
|||
readOnlyEnv(): boolean {
|
||||
return this.sourceControlStore.preferences.branchReadOnly;
|
||||
},
|
||||
showIOSearch(): boolean {
|
||||
return this.hasNodeRun && !this.hasRunError;
|
||||
},
|
||||
showIoSearchNoMatchContent(): boolean {
|
||||
return this.hasNodeRun && !this.inputData.length && this.search;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getResolvedNodeOutputs() {
|
||||
|
@ -1158,10 +1260,13 @@ export default defineComponent({
|
|||
getRunLabel(option: number) {
|
||||
let itemsCount = 0;
|
||||
for (let i = 0; i <= this.maxOutputIndex; i++) {
|
||||
itemsCount += this.getDataCount(option - 1, i);
|
||||
itemsCount += this.getPinDataOrLiveData(this.getRawInputData(option - 1, i)).length;
|
||||
}
|
||||
const items = this.$locale.baseText('ndv.output.items', { adjustToNumber: itemsCount });
|
||||
const itemsLabel = itemsCount > 0 ? ` (${itemsCount} ${items})` : '';
|
||||
const items = this.$locale.baseText('ndv.output.items', {
|
||||
adjustToNumber: itemsCount,
|
||||
interpolate: { count: itemsCount },
|
||||
});
|
||||
const itemsLabel = itemsCount > 0 ? ` (${items})` : '';
|
||||
return option + this.$locale.baseText('ndv.output.of') + (this.maxRunIndex + 1) + itemsLabel;
|
||||
},
|
||||
getRawInputData(
|
||||
|
@ -1201,6 +1306,14 @@ export default defineComponent({
|
|||
}
|
||||
return inputData;
|
||||
},
|
||||
getFilteredData(inputData: INodeExecutionData[]): INodeExecutionData[] {
|
||||
if (!this.search) {
|
||||
return inputData;
|
||||
}
|
||||
|
||||
this.currentPage = 1;
|
||||
return inputData.filter(({ json }) => searchInObject(json, this.search));
|
||||
},
|
||||
getDataCount(
|
||||
runIndex: number,
|
||||
outputIndex: number,
|
||||
|
@ -1215,7 +1328,8 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
const rawInputData = this.getRawInputData(runIndex, outputIndex, connectionType);
|
||||
return this.getPinDataOrLiveData(rawInputData).length;
|
||||
const pinOrLiveData = this.getPinDataOrLiveData(rawInputData);
|
||||
return this.getFilteredData(pinOrLiveData).length;
|
||||
},
|
||||
init() {
|
||||
// Reset the selected output index every time another node gets selected
|
||||
|
@ -1347,6 +1461,13 @@ export default defineComponent({
|
|||
});
|
||||
}
|
||||
},
|
||||
activatePane() {
|
||||
this.$emit('activatePane');
|
||||
},
|
||||
onSearchClear() {
|
||||
this.search = '';
|
||||
document.dispatchEvent(new KeyboardEvent('keyup', { key: '/' }));
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
node() {
|
||||
|
@ -1384,6 +1505,9 @@ export default defineComponent({
|
|||
branchIndex,
|
||||
});
|
||||
},
|
||||
search(newSearch: string) {
|
||||
this.$emit('search', newSearch);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -1465,24 +1589,32 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.itemsCount {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-left: var(--spacing-s);
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
|
||||
.runSelector {
|
||||
max-width: 210px;
|
||||
margin-left: var(--spacing-s);
|
||||
margin-bottom: var(--spacing-s);
|
||||
padding-left: var(--spacing-s);
|
||||
padding-bottom: var(--spacing-s);
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.runSelectorWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> * {
|
||||
margin-right: var(--spacing-4xs);
|
||||
}
|
||||
}
|
||||
|
||||
.pagination {
|
||||
|
@ -1645,3 +1777,14 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.highlight) {
|
||||
background-color: #f7dc55;
|
||||
color: black;
|
||||
border-radius: var(--border-radius-base);
|
||||
padding: 0 1px;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -43,11 +43,11 @@
|
|||
[$style.mappable]: mappingEnabled,
|
||||
[$style.dragged]: draggingPath === node.path,
|
||||
}"
|
||||
>"{{ node.key }}"</span
|
||||
>
|
||||
v-html="highlightSearchTerm(node.key)"
|
||||
/>
|
||||
</template>
|
||||
<template #renderNodeValue="{ node }">
|
||||
<span v-if="isNaN(node.index)">{{ getContent(node.content) }}</span>
|
||||
<span v-if="isNaN(node.index)" v-html="highlightSearchTerm(node.content)" />
|
||||
<span
|
||||
v-else
|
||||
data-target="mappable"
|
||||
|
@ -60,8 +60,8 @@
|
|||
[$style.dragged]: draggingPath === node.path,
|
||||
}"
|
||||
class="ph-no-capture"
|
||||
>{{ getContent(node.content) }}</span
|
||||
>
|
||||
v-html="highlightSearchTerm(node.content)"
|
||||
/>
|
||||
</template>
|
||||
</vue-json-pretty>
|
||||
</draggable>
|
||||
|
@ -74,7 +74,7 @@ import type { PropType } from 'vue';
|
|||
import VueJsonPretty from 'vue-json-pretty';
|
||||
import type { IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||
import Draggable from '@/components/Draggable.vue';
|
||||
import { executionDataToJson, isString, shorten } from '@/utils';
|
||||
import { executionDataToJson, highlightText, isString, sanitizeHtml, shorten } from '@/utils';
|
||||
import type { INodeUi } from '@/Interface';
|
||||
import { externalHooks } from '@/mixins/externalHooks';
|
||||
import { mapStores } from 'pinia';
|
||||
|
@ -125,6 +125,9 @@ export default defineComponent({
|
|||
totalRuns: {
|
||||
type: Number,
|
||||
},
|
||||
search: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const selectedJsonPath = ref(nonExistingJsonPath);
|
||||
|
@ -194,6 +197,9 @@ export default defineComponent({
|
|||
getListItemName(path: string): string {
|
||||
return path.replace(/^(\["?\d"?]\.?)/g, '');
|
||||
},
|
||||
highlightSearchTerm(value: string): string {
|
||||
return sanitizeHtml(highlightText(this.getContent(value), this.search));
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -18,6 +18,7 @@ type Props = {
|
|||
totalRuns: number;
|
||||
paneType: 'input' | 'output';
|
||||
node: INodeUi | null;
|
||||
search: string;
|
||||
};
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
|
@ -91,6 +92,7 @@ const onDragEnd = (el: HTMLElement) => {
|
|||
:draggingPath="draggingPath"
|
||||
:distanceFromActive="distanceFromActive"
|
||||
:node="node"
|
||||
:search="search"
|
||||
/>
|
||||
</div>
|
||||
</draggable>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import type { INodeUi, Schema } from '@/Interface';
|
||||
import { checkExhaustive, shorten } from '@/utils';
|
||||
import { checkExhaustive, highlightText, sanitizeHtml, shorten } from '@/utils';
|
||||
import { getMappedExpression } from '@/utils/mappingUtils';
|
||||
|
||||
type Props = {
|
||||
|
@ -14,6 +14,7 @@ type Props = {
|
|||
draggingPath: string;
|
||||
distanceFromActive: number;
|
||||
node: INodeUi | null;
|
||||
search: string;
|
||||
};
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
@ -26,8 +27,12 @@ const isFlat = computed(
|
|||
Array.isArray(props.schema.value) &&
|
||||
props.schema.value.every((v) => !Array.isArray(v.value)),
|
||||
);
|
||||
const key = computed((): string | undefined =>
|
||||
isSchemaParentTypeArray.value ? `[${props.schema.key}]` : props.schema.key,
|
||||
const key = computed((): string | undefined => {
|
||||
const highlightedKey = sanitizeHtml(highlightText(props.schema.key, props.search));
|
||||
return isSchemaParentTypeArray.value ? `[${highlightedKey}]` : highlightedKey;
|
||||
});
|
||||
const parentKey = computed((): string | undefined =>
|
||||
sanitizeHtml(highlightText(props.parent.key, props.search)),
|
||||
);
|
||||
const schemaName = computed(() =>
|
||||
isSchemaParentTypeArray.value ? `${props.schema.type}[${props.schema.key}]` : props.schema.key,
|
||||
|
@ -92,8 +97,8 @@ const getIconBySchemaType = (type: Schema['type']): string => {
|
|||
data-target="mappable"
|
||||
>
|
||||
<font-awesome-icon :icon="getIconBySchemaType(schema.type)" size="sm" />
|
||||
<span v-if="isSchemaParentTypeArray">{{ parent.key }}</span>
|
||||
<span v-if="key" :class="{ [$style.arrayIndex]: isSchemaParentTypeArray }">{{ key }}</span>
|
||||
<span v-if="isSchemaParentTypeArray" v-html="parentKey" />
|
||||
<span v-if="key" :class="{ [$style.arrayIndex]: isSchemaParentTypeArray }" v-html="key" />
|
||||
</span>
|
||||
</div>
|
||||
<span v-if="text" :class="$style.text">{{ text }}</span>
|
||||
|
@ -115,6 +120,7 @@ const getIconBySchemaType = (type: Schema['type']): string => {
|
|||
:distanceFromActive="distanceFromActive"
|
||||
:node="node"
|
||||
:style="{ transitionDelay: transitionDelay(i) }"
|
||||
:search="search"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
124
packages/editor-ui/src/components/RunDataSearch.vue
Normal file
124
packages/editor-ui/src/components/RunDataSearch.vue
Normal file
|
@ -0,0 +1,124 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, onMounted, onUnmounted } from 'vue';
|
||||
import { useI18n } from '@/composables';
|
||||
import type { NodePanelType } from '@/Interface';
|
||||
|
||||
type Props = {
|
||||
modelValue: string;
|
||||
paneType?: NodePanelType;
|
||||
isAreaActive?: boolean;
|
||||
};
|
||||
|
||||
const INITIAL_WIDTH = '34px';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: Props['modelValue']): void;
|
||||
(event: 'focus'): void;
|
||||
}>();
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
paneType: 'output',
|
||||
isAreaActive: false,
|
||||
});
|
||||
|
||||
const locale = useI18n();
|
||||
|
||||
const inputRef = ref<HTMLInputElement | null>(null);
|
||||
const maxWidth = ref(INITIAL_WIDTH);
|
||||
const opened = ref(false);
|
||||
const focused = ref(false);
|
||||
const placeholder = computed(() =>
|
||||
props.paneType === 'input'
|
||||
? locale.baseText('ndv.search.placeholder.input')
|
||||
: locale.baseText('ndv.search.placeholder.output'),
|
||||
);
|
||||
|
||||
const documentKeyHandler = (event: KeyboardEvent) => {
|
||||
const isTargetAnyFormElement =
|
||||
event.target instanceof HTMLInputElement ||
|
||||
event.target instanceof HTMLTextAreaElement ||
|
||||
event.target instanceof HTMLSelectElement;
|
||||
if (event.key === '/' && !focused.value && props.isAreaActive && !isTargetAnyFormElement) {
|
||||
inputRef.value?.focus();
|
||||
inputRef.value?.select();
|
||||
}
|
||||
};
|
||||
|
||||
const onSearchUpdate = (value: string) => {
|
||||
emit('update:modelValue', value);
|
||||
};
|
||||
const onFocus = () => {
|
||||
opened.value = true;
|
||||
focused.value = true;
|
||||
maxWidth.value = '30%';
|
||||
inputRef.value?.select();
|
||||
emit('focus');
|
||||
};
|
||||
const onBlur = () => {
|
||||
focused.value = false;
|
||||
if (!props.modelValue) {
|
||||
opened.value = false;
|
||||
maxWidth.value = INITIAL_WIDTH;
|
||||
}
|
||||
};
|
||||
onMounted(() => {
|
||||
document.addEventListener('keyup', documentKeyHandler);
|
||||
});
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('keyup', documentKeyHandler);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n8n-input
|
||||
ref="inputRef"
|
||||
data-test-id="ndv-search"
|
||||
:class="{
|
||||
[$style.ioSearch]: true,
|
||||
[$style.ioSearchOpened]: opened,
|
||||
}"
|
||||
:style="{ maxWidth }"
|
||||
:modelValue="modelValue"
|
||||
:placeholder="placeholder"
|
||||
size="small"
|
||||
@update:modelValue="onSearchUpdate"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
>
|
||||
<template #prefix>
|
||||
<n8n-icon :class="$style.ioSearchIcon" icon="search" />
|
||||
</template>
|
||||
</n8n-input>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
@import '@/styles/css-animation-helpers.scss';
|
||||
|
||||
.ioSearch {
|
||||
margin-right: var(--spacing-s);
|
||||
transition: max-width 0.3s $ease-out-expo;
|
||||
|
||||
.ioSearchIcon {
|
||||
color: var(--color-foreground-xdark);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.ioSearchOpened {
|
||||
.ioSearchIcon {
|
||||
cursor: default;
|
||||
}
|
||||
input {
|
||||
border: var(--input-border-color, var(--border-color-base))
|
||||
var(--input-border-style, var(--border-style-base)) var(--border-width-base);
|
||||
background: var(--input-background-color, var(--color-foreground-xlight));
|
||||
cursor: text;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -52,7 +52,7 @@
|
|||
[$style.draggingHeader]: isDragging,
|
||||
}"
|
||||
>
|
||||
<span>{{ column || ' ' }}</span>
|
||||
<span v-html="highlightSearchTerm(column || '')" />
|
||||
<div :class="$style.dragButton">
|
||||
<font-awesome-icon icon="grip-vertical" />
|
||||
</div>
|
||||
|
@ -120,8 +120,8 @@
|
|||
<span
|
||||
v-if="isSimple(data)"
|
||||
:class="{ [$style.value]: true, [$style.empty]: isEmpty(data) }"
|
||||
>{{ getValueToRender(data) }}</span
|
||||
>
|
||||
v-html="highlightSearchTerm(data)"
|
||||
/>
|
||||
<n8n-tree :nodeClass="$style.nodeClass" v-else :value="data">
|
||||
<template #label="{ label, path }">
|
||||
<span
|
||||
|
@ -141,9 +141,10 @@
|
|||
>
|
||||
</template>
|
||||
<template #value="{ value }">
|
||||
<span :class="{ [$style.nestedValue]: true, [$style.empty]: isEmpty(value) }">
|
||||
{{ getValueToRender(value) }}
|
||||
</span>
|
||||
<span
|
||||
:class="{ [$style.nestedValue]: true, [$style.empty]: isEmpty(value) }"
|
||||
v-html="highlightSearchTerm(value)"
|
||||
/>
|
||||
</template>
|
||||
</n8n-tree>
|
||||
</td>
|
||||
|
@ -160,7 +161,7 @@ import { defineComponent } from 'vue';
|
|||
import type { PropType } from 'vue';
|
||||
import { mapStores } from 'pinia';
|
||||
import type { INodeUi, ITableData, NDVState } from '@/Interface';
|
||||
import { getPairedItemId, shorten } from '@/utils';
|
||||
import { getPairedItemId, highlightText, sanitizeHtml, shorten } from '@/utils';
|
||||
import type { GenericValue, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||
import Draggable from './Draggable.vue';
|
||||
import { externalHooks } from '@/mixins/externalHooks';
|
||||
|
@ -205,6 +206,9 @@ export default defineComponent({
|
|||
hasDefaultHoverState: {
|
||||
type: Boolean,
|
||||
},
|
||||
search: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -360,7 +364,7 @@ export default defineComponent({
|
|||
value === undefined
|
||||
);
|
||||
},
|
||||
getValueToRender(value: unknown) {
|
||||
getValueToRender(value: unknown): string {
|
||||
if (value === '') {
|
||||
return this.$locale.baseText('runData.emptyString');
|
||||
}
|
||||
|
@ -376,8 +380,14 @@ export default defineComponent({
|
|||
if (value === null || value === undefined) {
|
||||
return `[${value}]`;
|
||||
}
|
||||
if (value === true || value === false || typeof value === 'number') {
|
||||
return value.toString();
|
||||
}
|
||||
return value;
|
||||
},
|
||||
highlightSearchTerm(value: string): string {
|
||||
return sanitizeHtml(highlightText(this.getValueToRender(value), this.search));
|
||||
},
|
||||
onDragStart() {
|
||||
this.draggedColumn = true;
|
||||
this.ndvStore.resetMappingTelemetry();
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
import userEvent from '@testing-library/user-event';
|
||||
import { createPinia, setActivePinia } from 'pinia';
|
||||
import { createComponentRenderer } from '@/__tests__/render';
|
||||
import RunDataSearch from '@/components/RunDataSearch.vue';
|
||||
import { useSettingsStore, useUIStore } from '@/stores';
|
||||
|
||||
const renderComponent = createComponentRenderer(RunDataSearch);
|
||||
let pinia: ReturnType<typeof createPinia>;
|
||||
let uiStore: ReturnType<typeof useUIStore>;
|
||||
let settingsStore: ReturnType<typeof useSettingsStore>;
|
||||
|
||||
describe('RunDataSearch', () => {
|
||||
beforeEach(() => {
|
||||
pinia = createPinia();
|
||||
setActivePinia(pinia);
|
||||
|
||||
uiStore = useUIStore();
|
||||
settingsStore = useSettingsStore();
|
||||
});
|
||||
|
||||
it('should not be focused on keyboard shortcut when area is not active', async () => {
|
||||
const { emitted } = renderComponent({
|
||||
pinia,
|
||||
props: {
|
||||
modelValue: '',
|
||||
},
|
||||
});
|
||||
|
||||
await userEvent.keyboard('/');
|
||||
expect(emitted().focus).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should be focused on click regardless of active area and keyboard shortcut should work after', async () => {
|
||||
const { getByRole, emitted, rerender } = renderComponent({
|
||||
pinia,
|
||||
props: {
|
||||
modelValue: '',
|
||||
},
|
||||
});
|
||||
|
||||
await userEvent.click(getByRole('textbox'));
|
||||
expect(emitted().focus).toHaveLength(1);
|
||||
await userEvent.click(document.body);
|
||||
|
||||
await rerender({ isAreaActive: true });
|
||||
await userEvent.keyboard('/');
|
||||
expect(emitted().focus).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should be focused twice if area is already active', async () => {
|
||||
const { getByRole, emitted } = renderComponent({
|
||||
pinia,
|
||||
props: {
|
||||
modelValue: '',
|
||||
isAreaActive: true,
|
||||
},
|
||||
});
|
||||
|
||||
await userEvent.click(getByRole('textbox'));
|
||||
expect(emitted().focus).toHaveLength(1);
|
||||
await userEvent.click(document.body);
|
||||
|
||||
await userEvent.keyboard('/');
|
||||
expect(emitted().focus).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should select all text when focused', async () => {
|
||||
vi.spyOn(settingsStore, 'isEnterpriseFeatureEnabled', 'get').mockReturnValue(() => true);
|
||||
const { getByRole, emitted } = renderComponent({
|
||||
pinia,
|
||||
props: {
|
||||
modelValue: '',
|
||||
isAreaActive: true,
|
||||
},
|
||||
});
|
||||
|
||||
const input = getByRole('textbox');
|
||||
|
||||
await userEvent.click(input);
|
||||
expect(emitted().focus).toHaveLength(1);
|
||||
await userEvent.type(input, 'test');
|
||||
|
||||
await userEvent.click(document.body);
|
||||
await userEvent.click(input);
|
||||
expect(emitted().focus).toHaveLength(2);
|
||||
|
||||
const selectionStart = input.selectionStart;
|
||||
const selectionEnd = input.selectionEnd;
|
||||
const isSelected = selectionStart === 0 && selectionEnd === input.value.length;
|
||||
|
||||
expect(isSelected).toBe(true);
|
||||
});
|
||||
});
|
|
@ -621,6 +621,7 @@ export const ALLOWED_HTML_TAGS = [
|
|||
'small',
|
||||
'details',
|
||||
'summary',
|
||||
'mark',
|
||||
];
|
||||
|
||||
export const CLOUD_CHANGE_PLAN_PAGE = window.location.host.includes('stage-app.n8n.cloud')
|
||||
|
|
|
@ -779,7 +779,7 @@
|
|||
"ndv.output.all": "all",
|
||||
"ndv.output.branch": "Branch",
|
||||
"ndv.output.executing": "Executing node...",
|
||||
"ndv.output.items": "item | items",
|
||||
"ndv.output.items": "{count} item | {count} items",
|
||||
"ndv.output.noOutputData.message": "n8n stops executing the workflow when a node has no output data. You can change this default behaviour via",
|
||||
"ndv.output.noOutputData.message.settings": "Settings",
|
||||
"ndv.output.noOutputData.message.settingsOption": "> “Always Output Data”.",
|
||||
|
@ -1756,6 +1756,12 @@
|
|||
"ndv.trigger.pollingNode.executionsHelp.inactive": "<b>While building your workflow</b>, click the 'fetch' button to fetch a single mock event. It will show up in this editor.<br /><br /><b>Once you're happy with your workflow</b>, <a data-key=\"activate\">activate</a> it. Then n8n will regularly check {service} for new events, and execute this workflow if it finds any. These executions will show up in the <a data-key=\"executions\">executions list</a>, but not in the editor.",
|
||||
"ndv.trigger.pollingNode.executionsHelp.active": "<b>While building your workflow</b>, click the 'fetch' button to fetch a single mock event. It will show up in this editor.<br /><br /><b>Your workflow will also execute automatically</b>, since it's activated. n8n will regularly check {app_name} for new events, and execute this workflow if it finds any. These executions will show up in the <a data-key=\"executions\">executions list</a>, but not in the editor.",
|
||||
"ndv.trigger.webhookBasedNode.action": "Pull in events from {name}",
|
||||
"ndv.search.placeholder.output": "Filter output",
|
||||
"ndv.search.placeholder.input": "Filter input",
|
||||
"ndv.search.noMatch.title": "No matching items",
|
||||
"ndv.search.noMatch.description": "Try changing or {link} the filter to see more",
|
||||
"ndv.search.noMatch.description.link": "clearing",
|
||||
"ndv.search.items": "{matched} of {total} item | {matched} of {total} items",
|
||||
"updatesPanel.andIs": "and is",
|
||||
"updatesPanel.behindTheLatest": "behind the latest and greatest n8n",
|
||||
"updatesPanel.howToUpdateYourN8nVersion": "How to update your n8n version",
|
||||
|
|
41
packages/editor-ui/src/utils/__tests__/htmlUtils.test.ts
Normal file
41
packages/editor-ui/src/utils/__tests__/htmlUtils.test.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { highlightText } from '@/utils';
|
||||
|
||||
describe('highlightText', () => {
|
||||
it('should return original text if search parameter is an empty string', () => {
|
||||
const text = 'some text';
|
||||
const result = highlightText(text);
|
||||
expect(result).toBe(text);
|
||||
});
|
||||
|
||||
it('should return original text if it is an empty string', () => {
|
||||
const text = '';
|
||||
const result = highlightText(text, 'search');
|
||||
expect(result).toBe(text);
|
||||
});
|
||||
|
||||
it('should escape special characters in the search string', () => {
|
||||
const text = 'some text [example]';
|
||||
const result = highlightText(text, '[example]');
|
||||
expect(result).toBe('some text <mark class="highlight">[example]</mark>');
|
||||
});
|
||||
|
||||
it('should escape other special characters in the search string', () => {
|
||||
const text = 'phone number: +123-456-7890';
|
||||
const result = highlightText(text, '+123-456-7890');
|
||||
expect(result).toBe('phone number: <mark class="highlight">+123-456-7890</mark>');
|
||||
});
|
||||
|
||||
it('should highlight occurrences of the search string in text', () => {
|
||||
const text = 'example text example';
|
||||
const result = highlightText(text, 'example');
|
||||
expect(result).toBe(
|
||||
'<mark class="highlight">example</mark> text <mark class="highlight">example</mark>',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return original text if the search string is not found', () => {
|
||||
const text = 'some text';
|
||||
const result = highlightText(text, 'notfound');
|
||||
expect(result).toBe(text);
|
||||
});
|
||||
});
|
|
@ -63,3 +63,9 @@ export const getBannerRowHeight = async (): Promise<number> => {
|
|||
}, 0);
|
||||
});
|
||||
};
|
||||
|
||||
export const highlightText = (text: string, search = ''): string => {
|
||||
const pattern = search.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
|
||||
const regex = new RegExp(`(${pattern})`, 'gi');
|
||||
return search ? text?.replace(regex, '<mark class="highlight">$1</mark>') : text;
|
||||
};
|
||||
|
|
|
@ -16,5 +16,5 @@ export const searchInObject = (obj: ObjectOrArray, searchString: string): boolea
|
|||
(Array.isArray(obj) ? obj : Object.entries(obj)).some((entry) =>
|
||||
isObjectOrArray(entry)
|
||||
? searchInObject(entry, searchString)
|
||||
: entry?.toString().includes(searchString),
|
||||
: entry?.toString().toLowerCase().includes(searchString.toLowerCase()),
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue