From e66aad73d7012b124779ebd70b7ce4333f3a5d62 Mon Sep 17 00:00:00 2001 From: Sai Medhini Reddy Maryada <117196660+saimedhi@users.noreply.github.com> Date: Fri, 18 Oct 2024 09:33:46 -0700 Subject: [PATCH] Adding Flow Framework Dashboards integration tests (#1586) * Added Flow Framework Dashboards integ tests Signed-off-by: saimedhi * Added Flow Framework Dashboards integ tests Signed-off-by: saimedhi * Added Flow Framework Dashboards integ tests Signed-off-by: saimedhi * Update dashboards-flow-framework-e2e-workflow.yml Signed-off-by: Sai Medhini Reddy Maryada <117196660+saimedhi@users.noreply.github.com> * Update dashboards-flow-framework-e2e-workflow.yml Signed-off-by: Sai Medhini Reddy Maryada <117196660+saimedhi@users.noreply.github.com> * Added Flow Framework Dashboards integ tests Signed-off-by: saimedhi * Added Flow Framework Dashboards integ tests Signed-off-by: saimedhi * Added Flow Framework Dashboards integ tests fixes Signed-off-by: saimedhi * Added Flow Framework Dashboards integ tests fixes Signed-off-by: saimedhi * Added Flow Framework Dashboards integ tests fixes Signed-off-by: saimedhi * Adding Flow Framework Dashboards integration tests Signed-off-by: saimedhi * Adding Flow Framework Dashboards integration tests Signed-off-by: saimedhi * Added Flow Framework Dashboards integ tests fix flakiness Signed-off-by: saimedhi --------- Signed-off-by: saimedhi Signed-off-by: Sai Medhini Reddy Maryada <117196660+saimedhi@users.noreply.github.com> (cherry picked from commit 2f6c79028183a9c4a187bec71b82c725d5bc24da) --- ...dashboards-flow-framework-e2e-workflow.yml | 24 ++ .../create_connector.json | 29 ++ .../register_model.json | 62 ++++ .../semantic_search/import_workflow.json | 275 ++++++++++++++++++ .../semantic_search/ingest_response.json | 119 ++++++++ .../semantic_search/search_query.json | 9 + .../semantic_search/search_response.json | 24 ++ .../semantic_search/source_data.json | 30 ++ .../create_workflow_spec.js | 250 ++++++++++++++++ cypress/support/index.js | 1 + cypress/utils/commands.js | 4 + cypress/utils/constants.js | 1 + .../dashboards-flow-framework/commands.js | 140 +++++++++ .../dashboards-flow-framework/constants.js | 55 ++++ .../dashboards-flow-framework/helpers.js | 8 + site/index.html | 4 + site/js/dashboard.js | 6 + test_finder.sh | 1 + 18 files changed, 1042 insertions(+) create mode 100644 .github/workflows/dashboards-flow-framework-e2e-workflow.yml create mode 100644 cypress/fixtures/plugins/dashboards-flow-framework/create_connector.json create mode 100644 cypress/fixtures/plugins/dashboards-flow-framework/register_model.json create mode 100644 cypress/fixtures/plugins/dashboards-flow-framework/semantic_search/import_workflow.json create mode 100644 cypress/fixtures/plugins/dashboards-flow-framework/semantic_search/ingest_response.json create mode 100644 cypress/fixtures/plugins/dashboards-flow-framework/semantic_search/search_query.json create mode 100644 cypress/fixtures/plugins/dashboards-flow-framework/semantic_search/search_response.json create mode 100644 cypress/fixtures/plugins/dashboards-flow-framework/semantic_search/source_data.json create mode 100644 cypress/integration/plugins/dashboards-flow-framework/create_workflow_spec.js create mode 100644 cypress/utils/plugins/dashboards-flow-framework/commands.js create mode 100644 cypress/utils/plugins/dashboards-flow-framework/constants.js create mode 100644 cypress/utils/plugins/dashboards-flow-framework/helpers.js diff --git a/.github/workflows/dashboards-flow-framework-e2e-workflow.yml b/.github/workflows/dashboards-flow-framework-e2e-workflow.yml new file mode 100644 index 000000000..493787e5d --- /dev/null +++ b/.github/workflows/dashboards-flow-framework-e2e-workflow.yml @@ -0,0 +1,24 @@ +name: Flow Framework Release tests workflow in Bundled OpenSearch Dashboards +on: + pull_request: + branches: [ '**' ] +jobs: + changes: + runs-on: ubuntu-latest + outputs: + tests: ${{ steps.filter.outputs.tests }} + steps: + - uses: dorny/paths-filter@v2 + id: filter + with: + filters: | + tests: + - 'cypress/**/dashboards-flow-framework/**' + + tests: + needs: changes + if: ${{ needs.changes.outputs.tests == 'true' }} + uses: ./.github/workflows/release-e2e-workflow-template.yml + with: + test-name: Flow Framework + test-command: env CYPRESS_NO_COMMAND_LOG=1 yarn cypress:run-with-security --browser chromium --spec 'cypress/integration/plugins/dashboards-flow-framework/*' diff --git a/cypress/fixtures/plugins/dashboards-flow-framework/create_connector.json b/cypress/fixtures/plugins/dashboards-flow-framework/create_connector.json new file mode 100644 index 000000000..2b992ee0a --- /dev/null +++ b/cypress/fixtures/plugins/dashboards-flow-framework/create_connector.json @@ -0,0 +1,29 @@ +{ + "name": "Amazon Bedrock - Titan", + "description": "The connector to BedRock Titan embedding model", + "version": 1, + "protocol": "aws_sigv4", + "parameters": { + "region": "us-west-2", + "service_name": "bedrock", + "model": "amazon.titan-embed-text-v1" + }, + "credential": { + "access_key": "", + "secret_key": "", + "session_token": "" + + }, + "actions": [ + { + "action_type": "predict", + "method": "POST", + "url": "https://bedrock-runtime.${parameters.region}.amazonaws.com/model/${parameters.model}/invoke", + "headers": { + "content-type": "application/json", + "x-amz-content-sha256": "required" + }, + "request_body": "{ \"inputText\": \"${parameters.inputText}\" }" + } + ] +} diff --git a/cypress/fixtures/plugins/dashboards-flow-framework/register_model.json b/cypress/fixtures/plugins/dashboards-flow-framework/register_model.json new file mode 100644 index 000000000..77192cd8e --- /dev/null +++ b/cypress/fixtures/plugins/dashboards-flow-framework/register_model.json @@ -0,0 +1,62 @@ +{ + "name": "BedRock Titan embedding model", + "version": "1.0.1", + "function_name": "remote", + "description": "", + "connector_id": "connector_id", + "interface": { + "input": { + "type": "object", + "properties": { + "parameters": { + "type": "object", + "properties": { + "inputText": { + "type": "string" + } + }, + "additionalProperties": true + } + } + }, + "output": { + "type": "object", + "properties": { + "inference_results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "output": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "dataAsMap": { + "type": "object", + "properties": { + "embedding": { + "type": "array" + } + }, + "required": ["embedding"] + } + }, + "required": ["name", "dataAsMap"] + } + }, + "status_code": { + "type": "integer" + } + }, + "required": ["output", "status_code"] + } + } + }, + "required": ["inference_results"] + } + } +} diff --git a/cypress/fixtures/plugins/dashboards-flow-framework/semantic_search/import_workflow.json b/cypress/fixtures/plugins/dashboards-flow-framework/semantic_search/import_workflow.json new file mode 100644 index 000000000..3b7498c66 --- /dev/null +++ b/cypress/fixtures/plugins/dashboards-flow-framework/semantic_search/import_workflow.json @@ -0,0 +1,275 @@ +{ + "name": "semantic_search_1", + "use_case": "", + "description": "A basic workflow containing the ingest pipeline, search pipeline, and index configurations for performing semantic search", + "version": { + "template": "1.0.0", + "compatibility": ["2.17.0", "3.0.0"] + }, + "workflows": { + "provision": { + "user_params": {}, + "nodes": [ + { + "id": "ingest_pipeline_4112b7671c507097", + "type": "create_ingest_pipeline", + "previous_node_inputs": {}, + "user_inputs": { + "configurations": "{\"description\":\"An ingest pipeline\",\"processors\":[{\"ml_inference\":{\"model_id\":\"jUssbZIBtUlObUc3aHho\",\"input_map\":[{\"inputText\":\"item_text\"}],\"output_map\":[{\"my_embedding\":\"embedding\"}],\"full_response_path\":false,\"ignore_missing\":false,\"ignore_failure\":false,\"model_config\":{}}}]}", + "pipeline_id": "ingest_pipeline_4112b7671c507097" + } + }, + { + "id": "search_pipeline_bbfed4e51c5f8b65", + "type": "create_search_pipeline", + "previous_node_inputs": {}, + "user_inputs": { + "configurations": "{\"request_processors\":[{\"ml_inference\":{\"model_id\":\"jUssbZIBtUlObUc3aHho\",\"input_map\":[{\"inputText\":\"query.term.item_text.value\"}],\"output_map\":[{\"vector\":\"embedding\"}],\"query_template\":\"{\\n \\\"_source\\\": {\\n \\\"excludes\\\": [\\n \\\"my_embedding\\\"\\n ]\\n },\\n \\\"query\\\": {\\n \\\"knn\\\": {\\n \\\"my_embedding\\\": {\\n \\\"vector\\\": ${vector},\\n \\\"k\\\": 10\\n }\\n }\\n }\\n}\",\"full_response_path\":false,\"ignore_missing\":false,\"ignore_failure\":false,\"model_config\":{}}}],\"response_processors\":[],\"phase_results_processors\":[]}", + "pipeline_id": "search_pipeline_bbfed4e51c5f8b65" + } + }, + { + "id": "knn_index_adda44", + "type": "create_index", + "previous_node_inputs": { + "ingest_pipeline_4112b7671c507097": "pipeline_id", + "search_pipeline_bbfed4e51c5f8b65": "pipeline_id" + }, + "user_inputs": { + "configurations": "{\"settings\":{\"index.knn\":true,\"default_pipeline\":\"${{ingest_pipeline_4112b7671c507097.pipeline_id}}\",\"index.search.default_pipeline\":\"${{search_pipeline_bbfed4e51c5f8b65.pipeline_id}}\"},\"mappings\":{\"properties\":{\"item_text\":{\"type\":\"text\"},\"my_embedding\":{\"type\":\"knn_vector\",\"dimension\":1536}}}}", + "index_name": "knn_index_adda44" + } + } + ], + "edges": [ + { + "source": "ingest_pipeline_4112b7671c507097", + "dest": "knn_index_adda44" + }, + { + "source": "search_pipeline_bbfed4e51c5f8b65", + "dest": "knn_index_adda44" + } + ] + } + }, + "ui_metadata": { + "type": "Semantic search", + "config": { + "search": { + "pipelineName": { + "id": "pipelineName", + "type": "string", + "value": "search_pipeline_bbfed4e51c5f8b65" + }, + "request": { + "id": "request", + "type": "json", + "value": "{\n \"query\": {\n \"term\": {\n \"item_text\": {\n \"value\": \"shoes\"\n }\n }\n }\n}" + }, + "index": { + "name": { + "id": "indexName", + "type": "string", + "value": "knn_index_adda44" + } + }, + "enrichRequest": { + "processors": [ + { + "name": "ML Inference Processor", + "id": "ml_processor_search_request_664d078f4e3c6083", + "fields": [ + { + "id": "model", + "type": "model", + "value": { + "id": "jUssbZIBtUlObUc3aHho" + } + }, + { + "id": "input_map", + "type": "mapArray", + "value": [ + [ + { + "value": "query.term.item_text.value", + "key": "inputText" + } + ] + ] + }, + { + "id": "output_map", + "type": "mapArray", + "value": [ + [ + { + "value": "embedding", + "key": "vector" + } + ] + ] + } + ], + "type": "ml_inference", + "optionalFields": [ + { + "id": "query_template", + "type": "jsonString", + "value": "{\n \"_source\": {\n \"excludes\": [\n \"my_embedding\"\n ]\n },\n \"query\": {\n \"knn\": {\n \"my_embedding\": {\n \"vector\": ${vector},\n \"k\": 10\n }\n }\n }\n}" + }, + { + "id": "model_config", + "type": "json", + "value": "{}" + }, + { + "id": "full_response_path", + "type": "boolean", + "value": false + }, + { + "id": "ignore_missing", + "type": "boolean", + "value": false + }, + { + "id": "ignore_failure", + "type": "boolean", + "value": false + }, + { + "id": "max_prediction_tasks", + "type": "number", + "value": 10 + }, + { + "id": "tag", + "type": "string", + "value": "" + }, + { + "id": "description", + "type": "string", + "value": "" + } + ] + } + ] + }, + "enrichResponse": { + "processors": [] + } + }, + "ingest": { + "pipelineName": { + "id": "pipelineName", + "type": "string", + "value": "ingest_pipeline_4112b7671c507097" + }, + "enrich": { + "processors": [ + { + "name": "ML Inference Processor", + "id": "ml_processor_ingest_f32b4938df7a4cc2", + "fields": [ + { + "id": "model", + "type": "model", + "value": { + "id": "jUssbZIBtUlObUc3aHho" + } + }, + { + "id": "input_map", + "type": "mapArray", + "value": [ + [ + { + "value": "item_text", + "key": "inputText" + } + ] + ] + }, + { + "id": "output_map", + "type": "mapArray", + "value": [ + [ + { + "value": "embedding", + "key": "my_embedding" + } + ] + ] + } + ], + "type": "ml_inference", + "optionalFields": [ + { + "id": "model_config", + "type": "json", + "value": "{}" + }, + { + "id": "full_response_path", + "type": "boolean", + "value": false + }, + { + "id": "ignore_missing", + "type": "boolean", + "value": false + }, + { + "id": "ignore_failure", + "type": "boolean", + "value": false + }, + { + "id": "max_prediction_tasks", + "type": "number", + "value": 10 + }, + { + "id": "tag", + "type": "string", + "value": "" + }, + { + "id": "description", + "type": "string", + "value": "" + } + ] + } + ] + }, + "index": { + "settings": { + "id": "indexSettings", + "type": "json", + "value": "{\n \"index.knn\": true\n}" + }, + "mappings": { + "id": "indexMappings", + "type": "json", + "value": "{\n \"properties\": {\n \"item_text\": {\n \"type\": \"text\"\n },\n \"my_embedding\": {\n \"type\": \"knn_vector\",\n \"dimension\": 1536\n }\n }\n}" + }, + "name": { + "id": "indexName", + "type": "string", + "value": "knn_index_adda44" + } + }, + "enabled": { + "id": "enabled", + "type": "boolean", + "value": true + } + } + } + } +} diff --git a/cypress/fixtures/plugins/dashboards-flow-framework/semantic_search/ingest_response.json b/cypress/fixtures/plugins/dashboards-flow-framework/semantic_search/ingest_response.json new file mode 100644 index 000000000..86707c693 --- /dev/null +++ b/cypress/fixtures/plugins/dashboards-flow-framework/semantic_search/ingest_response.json @@ -0,0 +1,119 @@ +{ + "took": 54, + "ingest_took": 126, + "errors": false, + "items": [ + { + "index": { + "_index": "knn_index", + "_id": "_5eb4274ecd07877b", + "_version": 1, + "result": "created", + "_shards": { + "total": 2, + "successful": 1, + "failed": 0 + }, + "_seq_no": 0, + "_primary_term": 1, + "status": 201 + } + }, + { + "index": { + "_index": "knn_index", + "_id": "_5c2109b59f22a89b", + "_version": 1, + "result": "created", + "_shards": { + "total": 2, + "successful": 1, + "failed": 0 + }, + "_seq_no": 1, + "_primary_term": 1, + "status": 201 + } + }, + { + "index": { + "_index": "knn_index", + "_id": "_1a09251a492ae577", + "_version": 1, + "result": "created", + "_shards": { + "total": 2, + "successful": 1, + "failed": 0 + }, + "_seq_no": 2, + "_primary_term": 1, + "status": 201 + } + }, + { + "index": { + "_index": "knn_index", + "_id": "_2a44dbf9e356a851", + "_version": 1, + "result": "created", + "_shards": { + "total": 2, + "successful": 1, + "failed": 0 + }, + "_seq_no": 3, + "_primary_term": 1, + "status": 201 + } + }, + { + "index": { + "_index": "knn_index", + "_id": "_0e3c03e40a1ccdd2", + "_version": 1, + "result": "created", + "_shards": { + "total": 2, + "successful": 1, + "failed": 0 + }, + "_seq_no": 4, + "_primary_term": 1, + "status": 201 + } + }, + { + "index": { + "_index": "knn_index", + "_id": "_07be3515c6668af1", + "_version": 1, + "result": "created", + "_shards": { + "total": 2, + "successful": 1, + "failed": 0 + }, + "_seq_no": 5, + "_primary_term": 1, + "status": 201 + } + }, + { + "index": { + "_index": "knn_index", + "_id": "_fc3aa8e1c935cbfb", + "_version": 1, + "result": "created", + "_shards": { + "total": 2, + "successful": 1, + "failed": 0 + }, + "_seq_no": 6, + "_primary_term": 1, + "status": 201 + } + } + ] +} diff --git a/cypress/fixtures/plugins/dashboards-flow-framework/semantic_search/search_query.json b/cypress/fixtures/plugins/dashboards-flow-framework/semantic_search/search_query.json new file mode 100644 index 000000000..8572c101f --- /dev/null +++ b/cypress/fixtures/plugins/dashboards-flow-framework/semantic_search/search_query.json @@ -0,0 +1,9 @@ +{ + "query": { + "term": { + "item_text": { + "value": "shoes" + } + } + } +} diff --git a/cypress/fixtures/plugins/dashboards-flow-framework/semantic_search/search_response.json b/cypress/fixtures/plugins/dashboards-flow-framework/semantic_search/search_response.json new file mode 100644 index 000000000..a7a79e854 --- /dev/null +++ b/cypress/fixtures/plugins/dashboards-flow-framework/semantic_search/search_response.json @@ -0,0 +1,24 @@ +{ + "hits": { + "hits": [ + { + "_source": { + "shoes": [ + { + "item_text": "purpul high heels", + "item_price": 150 + }, + { + "item_text": "pair of jordans", + "item_price": 250 + }, + { + "item_text": "baseball cleats", + "item_price": 85 + } + ] + } + } + ] + } +} diff --git a/cypress/fixtures/plugins/dashboards-flow-framework/semantic_search/source_data.json b/cypress/fixtures/plugins/dashboards-flow-framework/semantic_search/source_data.json new file mode 100644 index 000000000..7346ee8bc --- /dev/null +++ b/cypress/fixtures/plugins/dashboards-flow-framework/semantic_search/source_data.json @@ -0,0 +1,30 @@ +[ + { + "item_text": "purpul high heels", + "item_price": 150 + }, + { + "item_text": "pair of jordans", + "item_price": 250 + }, + { + "item_text": "baseball cleats", + "item_price": 85 + }, + { + "item_text": "red pants", + "item_price": 95 + }, + { + "item_text": "blue skirt", + "item_price": 55 + }, + { + "item_text": "orange tank top", + "item_price": 30 + }, + { + "item_text": "maroon shorts", + "item_price": 35 + } +] diff --git a/cypress/integration/plugins/dashboards-flow-framework/create_workflow_spec.js b/cypress/integration/plugins/dashboards-flow-framework/create_workflow_spec.js new file mode 100644 index 000000000..1f759a819 --- /dev/null +++ b/cypress/integration/plugins/dashboards-flow-framework/create_workflow_spec.js @@ -0,0 +1,250 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + FF_URL, + FF_FIXTURE_BASE_PATH, + WORKFLOW_DETAIL_URL_SEGMENT, + FF_TIMEOUT, +} from '../../../utils/constants'; +import createConnectorBody from '../../../fixtures/plugins/dashboards-flow-framework/create_connector.json'; +import registerModelBody from '../../../fixtures/plugins/dashboards-flow-framework/register_model.json'; +import { getLastPathSegment } from '../../../utils/plugins/dashboards-flow-framework/helpers'; + +describe('Creating Workflows Using Various Methods', () => { + var modelId = ''; + + before(() => { + cy.createConnector(createConnectorBody) + .then((connectorResponse) => { + return cy.registerModel({ + body: { + ...registerModelBody, + connector_id: connectorResponse.connector_id, + function_name: 'remote', + }, + }); + }) + .then((modelResponse) => { + modelId = modelResponse.model_id; + return cy.deployModel(modelId); + }); + }); + + beforeEach(() => { + cy.visit(FF_URL.WORKFLOWS_NEW); + cy.url().should('include', getLastPathSegment(FF_URL.WORKFLOWS_NEW)); + }); + + it('create workflow using import', () => { + cy.getElementByDataTestId('importWorkflowButton', { timeout: FF_TIMEOUT }) + .should('be.visible') + .click(); + cy.contains('Import a workflow (JSON/YAML)').should('be.visible'); + const filePath = + 'cypress/fixtures/' + + FF_FIXTURE_BASE_PATH + + 'semantic_search/import_workflow.json'; + cy.get('input[type=file]').selectFile(filePath); + cy.getElementByDataTestId('importJSONButton').should('be.visible').click(); + cy.get('.euiFieldSearch').should('be.visible').focus(); + cy.wait(1000); + cy.get('.euiFieldSearch') + .should('be.visible') + .type('semantic_search_1{enter}'); + cy.contains('semantic_search_1'); + cy.get('.euiTableRow').should('have.length.greaterThan', 0); + cy.get('.euiTableRow').first().find('button.euiButtonIcon--danger').click(); + cy.contains('The workflow will be permanently deleted.').should('exist'); + cy.getElementByDataTestId('deleteWorkflowButton') + .should('be.visible') + .click(); + }); + + it('Workflow Creation with Improper Import File', () => { + cy.getElementByDataTestId('importWorkflowButton') + .should('be.visible') + .click(); + cy.contains('Import a workflow (JSON/YAML)').should('be.visible'); + const filePath = + 'cypress/fixtures/' + + FF_FIXTURE_BASE_PATH + + 'semantic_search/search_query.json'; + cy.get('input[type=file]').selectFile(filePath); + cy.contains('The uploaded file is not a valid workflow').should( + 'be.visible' + ); + }); + + it('create workflow using Semantic Search template', () => { + cy.contains('h3', 'Semantic Search', { timeout: FF_TIMEOUT }) + .should('be.visible') + .parents('.euiCard') + .within(() => { + cy.contains('button', 'Go').click(); + }); + cy.getElementByDataTestId('optionalConfigurationButton') + .should('be.visible') + .click(); + cy.getElementByDataTestId('selectDeployedModel') + .should('be.visible') + .click(); + cy.get('.euiSuperSelect__item').should('be.visible'); + cy.get('.euiSuperSelect__item').contains('BedRock').click(); + cy.contains('label', 'Text field') + .invoke('attr', 'for') + .then((id) => { + cy.get(`#${id}`).clear().type('item_text'); + }); + cy.getElementByDataTestId('quickConfigureCreateButton') + .should('be.visible') + .click(); + cy.url().should('include', '/workflows/'); + cy.getElementByDataTestId('editSourceDataButton') + .should('be.visible') + .click(); + cy.getElementByDataTestId('uploadSourceDataButton') + .should('be.visible') + .click(); + const filePath = `cypress/fixtures/${FF_FIXTURE_BASE_PATH}semantic_search/source_data.json`; + cy.get('input[type=file]').selectFile(filePath); + cy.getElementByDataTestId('closeSourceDataButton') + .should('be.visible') + .click(); + cy.mockIngestion(() => { + cy.getElementByDataTestId('runIngestionButton') + .should('be.visible') + .click(); + }); + // Checking Run ingestion response + cy.sa_getElementByText('button.euiTab', 'Ingest response') + .should('be.visible') + .click(); + cy.fixture(FF_FIXTURE_BASE_PATH + 'semantic_search/ingest_response').then( + (expectedJson) => { + cy.get('#tools_panel_id .ace_editor .ace_content') + .should('be.visible') + .invoke('text') + .then((editorText) => { + expect(editorText).to.include(`"took": ${expectedJson.took}`); + expect(editorText).to.include( + `"ingest_took": ${expectedJson.ingest_took}` + ); + expect(editorText).to.include(`"errors": ${expectedJson.errors}`); + expect(editorText).to.include( + `"result": "${expectedJson.items[0].index.result}"` + ); + }); + } + ); + cy.getElementByDataTestId('searchPipelineButton') + .should('be.visible') + .click(); + cy.getElementByDataTestId('queryEditButton').should('be.visible').click(); + cy.get('[data-testid="editQueryModalBody"]').within(() => { + cy.fixture( + FF_FIXTURE_BASE_PATH + 'semantic_search/search_query.json' + ).then((jsonData) => { + const jsonString = JSON.stringify(jsonData); + cy.get('.ace_text-input') + .focus() + .clear({ force: true }) + .focus() + .wait(2000) + .type(jsonString, { + force: true, + parseSpecialCharSequences: false, + delay: 5, + }) + .trigger('blur', { force: true }); + }); + }); + cy.getElementByDataTestId('searchQueryCloseButton') + .should('be.visible') + .click(); + cy.mockSemanticSearchIndexSearch(() => { + cy.getElementByDataTestId('runQueryButton').should('be.visible').click(); + }); + // Checking Run query response + cy.sa_getElementByText('button.euiTab', 'Search response') + .should('be.visible') + .click(); + + cy.fixture(FF_FIXTURE_BASE_PATH + 'semantic_search/search_response').then( + (expectedSearchJson) => { + cy.get('#tools_panel_id .ace_editor .ace_content') + .should('be.visible') + .invoke('text') + .then((editorText) => { + const editorJson = JSON.parse(editorText); + expect(JSON.stringify(editorJson)).to.contain( + JSON.stringify(expectedSearchJson['hits']['hits'][0]['_source']) + ); + }); + } + ); + }); + + it('create workflow using Sentiment Analysis template', () => { + cy.contains('h3', 'Sentiment Analysis', { timeout: FF_TIMEOUT }) + .should('be.visible') + .parents('.euiCard') + .within(() => { + cy.contains('button', 'Go').click(); + }); + cy.getElementByDataTestId('quickConfigureCreateButton') + .should('be.visible') + .click(); + cy.url().should('include', WORKFLOW_DETAIL_URL_SEGMENT); + }); + + it('create workflow using Hybrid Search template', () => { + cy.contains('h3', 'Hybrid Search', { timeout: FF_TIMEOUT }) + .should('be.visible') + .parents('.euiCard') + .within(() => { + cy.contains('button', 'Go').click(); + }); + cy.getElementByDataTestId('quickConfigureCreateButton') + .should('be.visible') + .click(); + cy.url().should('include', WORKFLOW_DETAIL_URL_SEGMENT); + }); + + it('create workflow using Multimodal Search template', () => { + cy.contains('h3', 'Multimodal Search', { timeout: FF_TIMEOUT }) + .should('be.visible') + .parents('.euiCard') + .within(() => { + cy.contains('button', 'Go').click(); + }); + cy.getElementByDataTestId('quickConfigureCreateButton') + .should('be.visible') + .click(); + cy.url().should('include', WORKFLOW_DETAIL_URL_SEGMENT); + }); + + it('create workflow using Retrieval-Augmented Generation (RAG) template', () => { + cy.contains('h3', 'Retrieval-Augmented Generation (RAG)', { + timeout: FF_TIMEOUT, + }) + .should('be.visible') + .parents('.euiCard') + .within(() => { + cy.contains('button', 'Go').click(); + }); + cy.getElementByDataTestId('quickConfigureCreateButton') + .should('be.visible') + .click(); + cy.url().should('include', WORKFLOW_DETAIL_URL_SEGMENT); + }); + + after(() => { + if (modelId != '') { + cy.undeployMLCommonsModel(modelId); + cy.deleteMLCommonsModel(modelId); + } + }); +}); diff --git a/cypress/support/index.js b/cypress/support/index.js index ee60936d9..0033fdce4 100644 --- a/cypress/support/index.js +++ b/cypress/support/index.js @@ -27,6 +27,7 @@ import '../utils/plugins/anomaly-detection-dashboards-plugin/commands'; import '../utils/plugins/security/commands'; import '../utils/plugins/security-dashboards-plugin/commands'; import '../utils/plugins/alerting-dashboards-plugin/commands'; +import '../utils/plugins/dashboards-flow-framework/commands'; import '../utils/plugins/ml-commons-dashboards/commands'; import '../utils/plugins/security-analytics-dashboards-plugin/commands'; import '../utils/plugins/notifications-dashboards/commands'; diff --git a/cypress/utils/commands.js b/cypress/utils/commands.js index c09198dd9..77cc341aa 100644 --- a/cypress/utils/commands.js +++ b/cypress/utils/commands.js @@ -270,6 +270,10 @@ Cypress.Commands.add('getElementsByTestIds', (testIds, options = {}) => { return cy.get(selectors.join(','), options); }); +Cypress.Commands.add('getElementByDataTestId', (testId) => { + return cy.get(`[data-testid="${testId}"]`); +}); + Cypress.Commands.add( 'whenTestIdNotFound', (testIds, callbackFn, options = {}) => { diff --git a/cypress/utils/constants.js b/cypress/utils/constants.js index fb65ceaa7..3a601d669 100644 --- a/cypress/utils/constants.js +++ b/cypress/utils/constants.js @@ -7,6 +7,7 @@ export * from './base_constants'; export * from './dashboards/constants'; export * from './plugins/alerting-dashboards-plugin/constants'; export * from './plugins/anomaly-detection-dashboards-plugin/constants'; +export * from './plugins/dashboards-flow-framework/constants'; export * from './plugins/index-management-dashboards-plugin/constants'; export * from './plugins/observability-dashboards/constants'; export * from './plugins/reports-dashboards/constants'; diff --git a/cypress/utils/plugins/dashboards-flow-framework/commands.js b/cypress/utils/plugins/dashboards-flow-framework/commands.js new file mode 100644 index 000000000..2fddd7ce0 --- /dev/null +++ b/cypress/utils/plugins/dashboards-flow-framework/commands.js @@ -0,0 +1,140 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + FF_FIXTURE_BASE_PATH, + INGEST_NODE_API_PATH, + SEARCH_NODE_API_PATH, +} from '../../../utils/constants'; + +Cypress.Commands.add('createConnector', (connectorBody) => + cy + .request({ + method: 'POST', + form: false, + url: 'api/console/proxy', + headers: { + 'content-type': 'application/json;charset=UTF-8', + 'osd-xsrf': true, + }, + qs: { + path: '_plugins/_ml/connectors/_create', + method: 'POST', + }, + body: connectorBody, + }) + .then(({ body }) => body) +); + +Cypress.Commands.add('registerModel', ({ body }) => + cy + .request({ + method: 'POST', + form: false, + url: 'api/console/proxy', + headers: { + 'content-type': 'application/json;charset=UTF-8', + 'osd-xsrf': true, + }, + qs: { + path: '_plugins/_ml/models/_register', + method: 'POST', + }, + body: body, + }) + .then(({ body }) => { + return body; + }) +); + +Cypress.Commands.add('deployModel', (modelId) => + cy + .request({ + method: 'POST', + form: false, + url: 'api/console/proxy', + headers: { + 'content-type': 'application/json;charset=UTF-8', + 'osd-xsrf': true, + }, + qs: { + path: `_plugins/_ml/models/${modelId}/_deploy`, + method: 'POST', + }, + }) + .then(({ body }) => { + return body; + }) +); + +Cypress.Commands.add('undeployMLCommonsModel', (modelId) => + cy + .request({ + method: 'POST', + form: false, + url: 'api/console/proxy', + headers: { + 'content-type': 'application/json;charset=UTF-8', + 'osd-xsrf': true, + }, + qs: { + path: `_plugins/_ml/models` + `/${modelId}` + `/_undeploy`, + method: 'POST', + }, + }) + .then(({ body }) => body) +); + +Cypress.Commands.add('deleteMLCommonsModel', (modelId) => + cy + .request({ + method: 'POST', + form: false, + url: 'api/console/proxy', + headers: { + 'content-type': 'application/json;charset=UTF-8', + 'osd-xsrf': true, + }, + qs: { + path: `_plugins/_ml/models` + `/${modelId}`, + method: 'POST', + }, + }) + .then(({ body }) => body) +); + +Cypress.Commands.add('getWorkflowId', () => { + return cy.url().then((url) => { + return url.substring(url.lastIndexOf('/') + 1); + }); +}); + +Cypress.Commands.add('mockIngestion', (funcMockedOn) => { + cy.fixture( + FF_FIXTURE_BASE_PATH + 'semantic_search/ingest_response.json' + ).then((ingestResponse) => { + cy.intercept('POST', INGEST_NODE_API_PATH, { + statusCode: 200, + body: ingestResponse, + }).as('ingestionRequest'); + funcMockedOn(); + cy.wait('@ingestionRequest'); + }); +}); + +Cypress.Commands.add('mockSemanticSearchIndexSearch', (funcMockedOn) => { + cy.fixture( + FF_FIXTURE_BASE_PATH + 'semantic_search/search_response.json' + ).then((searchResults) => { + cy.intercept('POST', SEARCH_NODE_API_PATH + '/*', { + statusCode: 200, + body: searchResults, + }).as('searchRequest'); + + funcMockedOn(); + + cy.wait('@searchRequest'); + }); +}); diff --git a/cypress/utils/plugins/dashboards-flow-framework/constants.js b/cypress/utils/plugins/dashboards-flow-framework/constants.js new file mode 100644 index 000000000..5324e6c1a --- /dev/null +++ b/cypress/utils/plugins/dashboards-flow-framework/constants.js @@ -0,0 +1,55 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { BASE_PATH, BACKEND_BASE_PATH } from '../../base_constants'; +/** + ***************************** + URL CONSTANTS + ***************************** + */ + +const BASE_FF_PATH = BASE_PATH + '/app/search-studio#'; + +export const FF_URL = { + WORKFLOWS: BASE_FF_PATH + '/workflows', + WORKFLOWS_LIST: BASE_FF_PATH + '/workflows?tab=manage', + WORKFLOWS_NEW: BASE_FF_PATH + '/workflows?tab=create', +}; + +export const WORKFLOW_DETAIL_URL_SEGMENT = '/workflows/'; + +/** + ***************************** + PUBLIC API CONSTANTS + ***************************** + */ + +export const ML_COMMONS_APIS_PREFIX = BACKEND_BASE_PATH + '/_plugins/_ml'; +export const ML_MODELS_BASE_URL = `${ML_COMMONS_APIS_PREFIX}/models`; +export const APIS_MLC = { + CREATE_CONNECTOR_URL: `${ML_COMMONS_APIS_PREFIX}/connectors/_create`, + REGISTER_MODEL_URL: `${ML_MODELS_BASE_URL}/_register`, +}; + +/** + ***************************** + NODE API / SERVER CONSTANTS + ***************************** + */ + +export const BASE_FF_NODE_API_PATH = BASE_PATH + '/api/flow_framework'; +export const INGEST_NODE_API_PATH = BASE_FF_NODE_API_PATH + '/opensearch/bulk'; +export const SEARCH_NODE_API_PATH = + BASE_FF_NODE_API_PATH + '/opensearch/search'; + +/** + ***************************** + MISC CONSTANTS + ***************************** + */ + +export const FF_FIXTURE_BASE_PATH = 'plugins/dashboards-flow-framework/'; + +export const FF_TIMEOUT = 100000; diff --git a/cypress/utils/plugins/dashboards-flow-framework/helpers.js b/cypress/utils/plugins/dashboards-flow-framework/helpers.js new file mode 100644 index 000000000..a0418503b --- /dev/null +++ b/cypress/utils/plugins/dashboards-flow-framework/helpers.js @@ -0,0 +1,8 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export function getLastPathSegment(url) { + return url.split('/').pop(); +} diff --git a/site/index.html b/site/index.html index 6002aa724..c87d9c9cb 100644 --- a/site/index.html +++ b/site/index.html @@ -145,6 +145,10 @@

Plugins:

+ diff --git a/site/js/dashboard.js b/site/js/dashboard.js index fe18d2d8e..fd7013fc7 100644 --- a/site/js/dashboard.js +++ b/site/js/dashboard.js @@ -54,6 +54,12 @@ const plugins = { ], }, }, + 'dashboards-flow-framework': { + name: 'flowFrameworkDashboards', + default: { + videos: ['create_workflow_spec.js'], + }, + }, 'anomaly-detection-dashboards-plugin': { name: 'anomalyDetectionDashboards', default: { diff --git a/test_finder.sh b/test_finder.sh index d18cf24b5..248c039f7 100755 --- a/test_finder.sh +++ b/test_finder.sh @@ -12,6 +12,7 @@ CI_GROUP_PATTERN_PREFIX="OpenSearch-Dashboards-ci-group-" OSD_COMPONENT_TEST_MAP=( "OpenSearch-Dashboards:opensearch-dashboards" "alertingDashboards:alerting-dashboards-plugin" "anomalyDetectionDashboards:anomaly-detection-dashboards-plugin" + "flowFrameworkDashboards:dashboards-flow-framework" "ganttChartDashboards:gantt-chart-dashboards" "indexManagementDashboards:index-management-dashboards-plugin" "observabilityDashboards:observability-dashboards"