From 930f664dc3adebbdc61068370e8bf84f415b3f81 Mon Sep 17 00:00:00 2001 From: saimedhi Date: Wed, 4 Sep 2024 13:17:02 -0700 Subject: [PATCH 1/8] workflow_detail tests Signed-off-by: saimedhi --- .../workflow_detail/workflow_detail.test.tsx | 71 ++++++++++++++++ test/utils.ts | 81 +++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 public/pages/workflow_detail/workflow_detail.test.tsx create mode 100644 test/utils.ts diff --git a/public/pages/workflow_detail/workflow_detail.test.tsx b/public/pages/workflow_detail/workflow_detail.test.tsx new file mode 100644 index 00000000..f544aaa3 --- /dev/null +++ b/public/pages/workflow_detail/workflow_detail.test.tsx @@ -0,0 +1,71 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { Provider } from 'react-redux'; +import { + RouteComponentProps, + Route, + Switch, + MemoryRouter, +} from 'react-router-dom'; +import { WorkflowDetail } from './workflow_detail'; +import { WorkflowDetailRouterProps } from '../../pages'; +import '@testing-library/jest-dom'; +import { mockStore } from '../../../test/utils'; + +jest.mock('../../services', () => { + const { mockCoreServices } = require('../../../test'); + return { + ...jest.requireActual('../../services'), + ...mockCoreServices, + }; +}); + +const renderWithRouter = (initialEntries: string[]) => + render( + + + + ) => ( + + )} + /> + + + + ); + +describe('WorkflowDetail', () => { + test('renders the page with workflowId parameter', () => { + const workflowId = '12345'; + const { getAllByText, getByText, getByRole } = renderWithRouter([ + `/workflow/${workflowId}`, + ]); + + expect(getAllByText('test_workflow').length).toBeGreaterThan(0); + expect(getAllByText('Create an ingest pipeline').length).toBeGreaterThan(0); + expect(getAllByText('Skip ingestion pipeline').length).toBeGreaterThan(0); + expect(getAllByText('Define ingest pipeline').length).toBeGreaterThan(0); + expect(getAllByText('Tools').length).toBeGreaterThan(0); + expect(getAllByText('Preview').length).toBeGreaterThan(0); + expect(getAllByText('Not started').length).toBeGreaterThan(0); + expect( + getAllByText('Last updated: 09/03/24 06:34 PM').length + ).toBeGreaterThan(0); + expect(getAllByText('Search pipeline').length).toBeGreaterThan(0); + expect(getByText('Close')).toBeInTheDocument(); + expect(getByText('Export')).toBeInTheDocument(); + expect(getByText('Visual')).toBeInTheDocument(); + expect(getByText('JSON')).toBeInTheDocument(); + expect(getByRole('tab', { name: 'Run ingestion' })).toBeInTheDocument(); + expect(getByRole('tab', { name: 'Run queries' })).toBeInTheDocument(); + expect(getByRole('tab', { name: 'Errors' })).toBeInTheDocument(); + expect(getByRole('tab', { name: 'Resources' })).toBeInTheDocument(); + }); +}); diff --git a/test/utils.ts b/test/utils.ts new file mode 100644 index 00000000..17c9d335 --- /dev/null +++ b/test/utils.ts @@ -0,0 +1,81 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export const mockStore = { + getState: () => ({ + opensearch: { + errorMessage: '', + }, + workflows: { + loading: false, + errorMessage: '', + workflows: { + '12345': { + id: '12345', + name: 'test_workflow', + use_case: 'CUSTOM', + description: 'A blank workflow with no preset configurations', + version: { template: '1.0.0', compatibility: ['2.17.0', '3.0.0'] }, + workflows: {}, + ui_metadata: { + type: 'Custom', + config: { + search: { + pipelineName: { + id: 'pipelineName', + type: 'string', + value: 'search_pipeline_248e2f68b43db682', + }, + request: { + id: 'request', + type: 'json', + value: + '{\n "query": {\n "match_all": {}\n },\n "size": 1000\n}', + }, + index: { name: { id: 'indexName', type: 'string' } }, + enrichRequest: { processors: [] }, + enrichResponse: { processors: [] }, + }, + ingest: { + pipelineName: { + id: 'pipelineName', + type: 'string', + value: 'ingest_pipeline_7b139fd4eccac336', + }, + enrich: { processors: [] }, + index: { + settings: { id: 'indexSettings', type: 'json' }, + mappings: { + id: 'indexMappings', + type: 'json', + value: '{\n "properties": {}\n}', + }, + name: { + id: 'indexName', + type: 'string', + value: 'my-new-index', + }, + }, + enabled: { id: 'enabled', type: 'boolean', value: true }, + }, + }, + }, + lastUpdated: 1725413687437, + resourcesCreated: [], + }, + }, + }, + }), + dispatch: jest.fn(), + subscribe: jest.fn(), + replaceReducer: jest.fn(), + [Symbol.observable]: jest.fn(), +}; + +global.ResizeObserver = class { + observe() {} + unobserve() {} + disconnect() {} +}; From 34e96d1d2440d46aafcb899d0ee8dc46442dfc83 Mon Sep 17 00:00:00 2001 From: saimedhi Date: Wed, 4 Sep 2024 13:45:17 -0700 Subject: [PATCH 2/8] workflow_detail tests Signed-off-by: saimedhi --- public/pages/workflow_detail/workflow_detail.test.tsx | 3 ++- test/utils.ts | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/pages/workflow_detail/workflow_detail.test.tsx b/public/pages/workflow_detail/workflow_detail.test.tsx index f544aaa3..822c6bfa 100644 --- a/public/pages/workflow_detail/workflow_detail.test.tsx +++ b/public/pages/workflow_detail/workflow_detail.test.tsx @@ -56,7 +56,8 @@ describe('WorkflowDetail', () => { expect(getAllByText('Preview').length).toBeGreaterThan(0); expect(getAllByText('Not started').length).toBeGreaterThan(0); expect( - getAllByText('Last updated: 09/03/24 06:34 PM').length + getAllByText((content, element) => content.startsWith('Last updated:')) + .length ).toBeGreaterThan(0); expect(getAllByText('Search pipeline').length).toBeGreaterThan(0); expect(getByText('Close')).toBeInTheDocument(); diff --git a/test/utils.ts b/test/utils.ts index 17c9d335..7663f711 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -62,7 +62,6 @@ export const mockStore = { }, }, }, - lastUpdated: 1725413687437, resourcesCreated: [], }, }, From e85b0481af8978dc5147ac7ae2eb029eef0b2a9e Mon Sep 17 00:00:00 2001 From: saimedhi Date: Thu, 5 Sep 2024 10:10:49 -0700 Subject: [PATCH 3/8] workflow_detail tests Signed-off-by: saimedhi --- .../workflow_detail/workflow_detail.test.tsx | 123 +++++++-- test/utils.ts | 240 +++++++++++++----- 2 files changed, 279 insertions(+), 84 deletions(-) diff --git a/public/pages/workflow_detail/workflow_detail.test.tsx b/public/pages/workflow_detail/workflow_detail.test.tsx index 822c6bfa..8a88fd66 100644 --- a/public/pages/workflow_detail/workflow_detail.test.tsx +++ b/public/pages/workflow_detail/workflow_detail.test.tsx @@ -10,12 +10,15 @@ import { RouteComponentProps, Route, Switch, - MemoryRouter, + Router, + Redirect, } from 'react-router-dom'; import { WorkflowDetail } from './workflow_detail'; import { WorkflowDetailRouterProps } from '../../pages'; import '@testing-library/jest-dom'; -import { mockStore } from '../../../test/utils'; +import { mockStore, resizeObserverMock } from '../../../test/utils'; +import { createMemoryHistory } from 'history'; +import { WORKFLOW_TYPE } from '../../../common'; jest.mock('../../services', () => { const { mockCoreServices } = require('../../../test'); @@ -25,30 +28,116 @@ jest.mock('../../services', () => { }; }); -const renderWithRouter = (initialEntries: string[]) => - render( - - +const history = createMemoryHistory(); +window.ResizeObserver = resizeObserverMock; + +const renderWithRouter = ( + workflowId: string, + workflowName: string, + workflowType: WORKFLOW_TYPE, + includeProcessor: boolean +) => ({ + ...render( + + ) => ( - - )} + render={(props: RouteComponentProps) => { + return ; + }} /> + - + - ); + ), +}); + +const workflowId = '12345'; +const workflowName = 'test_workflow'; + +describe('WorkflowDetail', () => { + test('renders the page with Custom type', () => { + const { getAllByText, getByText, getByRole } = renderWithRouter( + workflowId, + workflowName, + WORKFLOW_TYPE.CUSTOM, + false + ); + + expect(getAllByText(workflowName).length).toBeGreaterThan(0); + expect(getAllByText('Create an ingest pipeline').length).toBeGreaterThan(0); + expect(getAllByText('Skip ingestion pipeline').length).toBeGreaterThan(0); + expect(getAllByText('Define ingest pipeline').length).toBeGreaterThan(0); + expect(getAllByText('Tools').length).toBeGreaterThan(0); + expect(getAllByText('Preview').length).toBeGreaterThan(0); + expect(getAllByText('Not started').length).toBeGreaterThan(0); + expect( + getAllByText((content, element) => content.startsWith('Last updated:')) + .length + ).toBeGreaterThan(0); + expect(getAllByText('Search pipeline').length).toBeGreaterThan(0); + expect(getByText('Close')).toBeInTheDocument(); + expect(getByText('Export')).toBeInTheDocument(); + expect(getByText('Visual')).toBeInTheDocument(); + expect(getByText('JSON')).toBeInTheDocument(); + expect(getByRole('tab', { name: 'Run ingestion' })).toBeInTheDocument(); + expect(getByRole('tab', { name: 'Run queries' })).toBeInTheDocument(); + expect(getByRole('tab', { name: 'Errors' })).toBeInTheDocument(); + expect(getByRole('tab', { name: 'Resources' })).toBeInTheDocument(); + }); +}); + +describe('WorkflowDetail', () => { + test('renders the page with Semantic Search type', () => { + const { getAllByText, getByText, getByRole } = renderWithRouter( + workflowId, + workflowName, + WORKFLOW_TYPE.SEMANTIC_SEARCH, + true + ); + + expect(getAllByText(workflowName).length).toBeGreaterThan(0); + expect(getAllByText('Create an ingest pipeline').length).toBeGreaterThan(0); + expect(getAllByText('Skip ingestion pipeline').length).toBeGreaterThan(0); + expect(getAllByText('Define ingest pipeline').length).toBeGreaterThan(0); + expect(getAllByText('Tools').length).toBeGreaterThan(0); + expect(getAllByText('Preview').length).toBeGreaterThan(0); + expect(getAllByText('Not started').length).toBeGreaterThan(0); + expect( + getAllByText((content, element) => content.startsWith('Last updated:')) + .length + ).toBeGreaterThan(0); + expect(getAllByText('Search pipeline').length).toBeGreaterThan(0); + expect(getByText('Close')).toBeInTheDocument(); + expect(getByText('Export')).toBeInTheDocument(); + expect(getByText('Visual')).toBeInTheDocument(); + expect(getByText('JSON')).toBeInTheDocument(); + expect(getByRole('tab', { name: 'Run ingestion' })).toBeInTheDocument(); + expect(getByRole('tab', { name: 'Run queries' })).toBeInTheDocument(); + expect(getByRole('tab', { name: 'Errors' })).toBeInTheDocument(); + expect(getByRole('tab', { name: 'Resources' })).toBeInTheDocument(); + }); +}); describe('WorkflowDetail', () => { - test('renders the page with workflowId parameter', () => { - const workflowId = '12345'; - const { getAllByText, getByText, getByRole } = renderWithRouter([ - `/workflow/${workflowId}`, - ]); + test('renders the page with Hybrid Search type', () => { + const { getAllByText, getByText, getByRole } = renderWithRouter( + workflowId, + workflowName, + WORKFLOW_TYPE.HYBRID_SEARCH, + true + ); - expect(getAllByText('test_workflow').length).toBeGreaterThan(0); + expect(getAllByText(workflowName).length).toBeGreaterThan(0); expect(getAllByText('Create an ingest pipeline').length).toBeGreaterThan(0); expect(getAllByText('Skip ingestion pipeline').length).toBeGreaterThan(0); expect(getAllByText('Define ingest pipeline').length).toBeGreaterThan(0); diff --git a/test/utils.ts b/test/utils.ts index 7663f711..340cb303 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -3,78 +3,184 @@ * SPDX-License-Identifier: Apache-2.0 */ -export const mockStore = { - getState: () => ({ - opensearch: { - errorMessage: '', +import { PROCESSOR_TYPE, WORKFLOW_TYPE } from '../common/constants'; +import { ProcessorsConfig, Workflow } from '../common/interfaces'; + +function generateWorkflow( + workflowId: string, + workflowName: string, + workflowType: WORKFLOW_TYPE, + includeProcessor: boolean = false +): Workflow { + return { + id: workflowId, + name: workflowName, + description: 'A blank workflow with no preset configurations', + version: { template: '1.0.0', compatibility: ['2.17.0', '3.0.0'] }, + ui_metadata: { + type: workflowType, + config: { + search: { + pipelineName: { + id: 'pipelineName', + type: 'string', + value: 'search_pipeline_248e2f68b43db682', + }, + request: { + id: 'request', + type: 'json', + value: + '{\n "query": {\n "match_all": {}\n },\n "size": 1000\n}', + }, + index: { name: { id: 'indexName', type: 'string' } }, + enrichRequest: getProcessor(includeProcessor), + enrichResponse: getProcessor(includeProcessor), + }, + ingest: { + pipelineName: { + id: 'pipelineName', + type: 'string', + value: 'ingest_pipeline_7b139fd4eccac336', + }, + enrich: getProcessor(includeProcessor), + index: { + settings: { id: 'indexSettings', type: 'json' }, + mappings: { + id: 'indexMappings', + type: 'json', + value: '{\n "properties": {}\n}', + }, + name: { + id: 'indexName', + type: 'string', + value: 'my-new-index', + }, + }, + enabled: { id: 'enabled', type: 'boolean', value: true }, + }, + }, }, - workflows: { - loading: false, - errorMessage: '', - workflows: { - '12345': { - id: '12345', - name: 'test_workflow', - use_case: 'CUSTOM', - description: 'A blank workflow with no preset configurations', - version: { template: '1.0.0', compatibility: ['2.17.0', '3.0.0'] }, - workflows: {}, - ui_metadata: { - type: 'Custom', - config: { - search: { - pipelineName: { - id: 'pipelineName', - type: 'string', - value: 'search_pipeline_248e2f68b43db682', - }, - request: { - id: 'request', - type: 'json', - value: - '{\n "query": {\n "match_all": {}\n },\n "size": 1000\n}', - }, - index: { name: { id: 'indexName', type: 'string' } }, - enrichRequest: { processors: [] }, - enrichResponse: { processors: [] }, + }; +} + +// TODO: In the code below, only the ml_inference processor has been added. Other processors still need to be included. +function getProcessor(includeProcessor: boolean): ProcessorsConfig { + if (includeProcessor) { + return { + processors: [ + { + name: 'ML Inference Processor', + id: 'ml_processor_ingest_d6d16748b3888061', + fields: [ + { + id: 'model', + type: 'model', + value: { + id: 'dfMPE5EB8_-RPNi-S0gD', }, - ingest: { - pipelineName: { - id: 'pipelineName', - type: 'string', - value: 'ingest_pipeline_7b139fd4eccac336', - }, - enrich: { processors: [] }, - index: { - settings: { id: 'indexSettings', type: 'json' }, - mappings: { - id: 'indexMappings', - type: 'json', - value: '{\n "properties": {}\n}', + }, + { + id: 'input_map', + type: 'mapArray', + value: [ + [ + { + value: 'my_text', + key: '', }, - name: { - id: 'indexName', - type: 'string', - value: 'my-new-index', + ], + ], + }, + { + id: 'output_map', + type: 'mapArray', + value: [ + [ + { + value: '', + key: 'my_embedding', }, - }, - enabled: { id: 'enabled', type: 'boolean', value: true }, - }, + ], + ], }, - }, - resourcesCreated: [], + ], + type: PROCESSOR_TYPE.ML, + optionalFields: [ + { + id: 'description', + type: 'string', + }, + { + id: 'model_config', + type: 'json', + }, + { + 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', + }, + ], }, + ], + }; + } else { + return { processors: [] }; + } +} + +export function mockStore( + workflowId: string, + workflowName: string, + workflowType: WORKFLOW_TYPE, + includeProcessor: boolean = false +) { + return { + getState: () => ({ + opensearch: { + errorMessage: '', }, - }, - }), - dispatch: jest.fn(), - subscribe: jest.fn(), - replaceReducer: jest.fn(), - [Symbol.observable]: jest.fn(), -}; + ml: {}, + workflows: { + loading: false, + errorMessage: '', + workflows: { + [workflowId]: generateWorkflow( + workflowId, + workflowName, + workflowType, + includeProcessor + ), + }, + }, + }), + dispatch: jest.fn(), + subscribe: jest.fn(), + replaceReducer: jest.fn(), + [Symbol.observable]: jest.fn(), + }; +} -global.ResizeObserver = class { - observe() {} - unobserve() {} - disconnect() {} -}; +export const resizeObserverMock = jest.fn().mockImplementation(() => ({ + observe: jest.fn(), + unobserve: jest.fn(), + disconnect: jest.fn(), +})); From 55e4c0f86a14f84069f5c5a77fa8c7f2f056aea2 Mon Sep 17 00:00:00 2001 From: saimedhi Date: Thu, 5 Sep 2024 11:19:17 -0700 Subject: [PATCH 4/8] workflow_detail tests Signed-off-by: saimedhi --- test/utils.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/test/utils.ts b/test/utils.ts index 340cb303..dc5abc7d 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -15,7 +15,6 @@ function generateWorkflow( return { id: workflowId, name: workflowName, - description: 'A blank workflow with no preset configurations', version: { template: '1.0.0', compatibility: ['2.17.0', '3.0.0'] }, ui_metadata: { type: workflowType, @@ -24,13 +23,12 @@ function generateWorkflow( pipelineName: { id: 'pipelineName', type: 'string', - value: 'search_pipeline_248e2f68b43db682', + value: jest.fn(), }, request: { id: 'request', type: 'json', - value: - '{\n "query": {\n "match_all": {}\n },\n "size": 1000\n}', + value: jest.fn(), }, index: { name: { id: 'indexName', type: 'string' } }, enrichRequest: getProcessor(includeProcessor), @@ -40,7 +38,7 @@ function generateWorkflow( pipelineName: { id: 'pipelineName', type: 'string', - value: 'ingest_pipeline_7b139fd4eccac336', + value: jest.fn(), }, enrich: getProcessor(includeProcessor), index: { @@ -48,12 +46,12 @@ function generateWorkflow( mappings: { id: 'indexMappings', type: 'json', - value: '{\n "properties": {}\n}', + value: jest.fn(), }, name: { id: 'indexName', type: 'string', - value: 'my-new-index', + value: jest.fn(), }, }, enabled: { id: 'enabled', type: 'boolean', value: true }, @@ -76,7 +74,7 @@ function getProcessor(includeProcessor: boolean): ProcessorsConfig { id: 'model', type: 'model', value: { - id: 'dfMPE5EB8_-RPNi-S0gD', + id: jest.fn(), }, }, { From 1e39db405b8635114b238281745bc9c87bfc6027 Mon Sep 17 00:00:00 2001 From: saimedhi Date: Thu, 5 Sep 2024 14:09:12 -0700 Subject: [PATCH 5/8] workflow_detail tests Signed-off-by: saimedhi --- .../workflow_detail/workflow_detail.test.tsx | 21 +- test/utils.ts | 316 +++++++++++------- 2 files changed, 208 insertions(+), 129 deletions(-) diff --git a/public/pages/workflow_detail/workflow_detail.test.tsx b/public/pages/workflow_detail/workflow_detail.test.tsx index 8a88fd66..2b8fccba 100644 --- a/public/pages/workflow_detail/workflow_detail.test.tsx +++ b/public/pages/workflow_detail/workflow_detail.test.tsx @@ -34,18 +34,10 @@ window.ResizeObserver = resizeObserverMock; const renderWithRouter = ( workflowId: string, workflowName: string, - workflowType: WORKFLOW_TYPE, - includeProcessor: boolean + workflowType: WORKFLOW_TYPE ) => ({ ...render( - + { const { getAllByText, getByText, getByRole } = renderWithRouter( workflowId, workflowName, - WORKFLOW_TYPE.CUSTOM, - false + WORKFLOW_TYPE.CUSTOM ); expect(getAllByText(workflowName).length).toBeGreaterThan(0); @@ -101,8 +92,7 @@ describe('WorkflowDetail', () => { const { getAllByText, getByText, getByRole } = renderWithRouter( workflowId, workflowName, - WORKFLOW_TYPE.SEMANTIC_SEARCH, - true + WORKFLOW_TYPE.SEMANTIC_SEARCH ); expect(getAllByText(workflowName).length).toBeGreaterThan(0); @@ -133,8 +123,7 @@ describe('WorkflowDetail', () => { const { getAllByText, getByText, getByRole } = renderWithRouter( workflowId, workflowName, - WORKFLOW_TYPE.HYBRID_SEARCH, - true + WORKFLOW_TYPE.HYBRID_SEARCH ); expect(getAllByText(workflowName).length).toBeGreaterThan(0); diff --git a/test/utils.ts b/test/utils.ts index dc5abc7d..8a51fa2a 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -4,13 +4,46 @@ */ import { PROCESSOR_TYPE, WORKFLOW_TYPE } from '../common/constants'; -import { ProcessorsConfig, Workflow } from '../common/interfaces'; +import { + IProcessorConfig, + ProcessorsConfig, + Workflow, +} from '../common/interfaces'; + +export function mockStore( + workflowId: string, + workflowName: string, + workflowType: WORKFLOW_TYPE +) { + return { + getState: () => ({ + opensearch: { + errorMessage: '', + }, + ml: {}, + workflows: { + loading: false, + errorMessage: '', + workflows: { + [workflowId]: generateWorkflow( + workflowId, + workflowName, + workflowType + ), + }, + }, + }), + dispatch: jest.fn(), + subscribe: jest.fn(), + replaceReducer: jest.fn(), + [Symbol.observable]: jest.fn(), + }; +} function generateWorkflow( workflowId: string, workflowName: string, - workflowType: WORKFLOW_TYPE, - includeProcessor: boolean = false + workflowType: WORKFLOW_TYPE ): Workflow { return { id: workflowId, @@ -23,35 +56,35 @@ function generateWorkflow( pipelineName: { id: 'pipelineName', type: 'string', - value: jest.fn(), + value: 'search_pipeline', }, request: { id: 'request', type: 'json', - value: jest.fn(), + value: '{\n "query": {\n "match_all": {}\n },\n "size": 1000\n}', }, index: { name: { id: 'indexName', type: 'string' } }, - enrichRequest: getProcessor(includeProcessor), - enrichResponse: getProcessor(includeProcessor), + enrichRequest: getRequestProcessor(workflowType), + enrichResponse: getResponseProcessor(workflowType), }, ingest: { pipelineName: { id: 'pipelineName', type: 'string', - value: jest.fn(), + value: 'ingest_pipeline', }, - enrich: getProcessor(includeProcessor), + enrich: getRequestProcessor(workflowType), index: { settings: { id: 'indexSettings', type: 'json' }, mappings: { id: 'indexMappings', type: 'json', - value: jest.fn(), + value: '{\n "properties": {}\n}', }, name: { id: 'indexName', type: 'string', - value: jest.fn(), + value: 'my-new-index', }, }, enabled: { id: 'enabled', type: 'boolean', value: true }, @@ -61,120 +94,177 @@ function generateWorkflow( }; } -// TODO: In the code below, only the ml_inference processor has been added. Other processors still need to be included. -function getProcessor(includeProcessor: boolean): ProcessorsConfig { - if (includeProcessor) { - return { - processors: [ +function getRequestProcessor(workflowType: WORKFLOW_TYPE): ProcessorsConfig { + if ( + workflowType === WORKFLOW_TYPE.HYBRID_SEARCH || + workflowType === WORKFLOW_TYPE.SEMANTIC_SEARCH + ) { + // TODO: In the code below, only the ml_inference processor has been added. Other processors still need to be included. + const mlInferenceProcessor: IProcessorConfig = { + name: 'ML Inference Processor', + id: 'ml_processor_ingest', + fields: [ { - name: 'ML Inference Processor', - id: 'ml_processor_ingest_d6d16748b3888061', - fields: [ - { - id: 'model', - type: 'model', - value: { - id: jest.fn(), + id: 'model', + type: 'model', + value: { + id: 'dfMPE5EB8_-RPNi-S0gD', + }, + }, + { + id: 'input_map', + type: 'mapArray', + value: [ + [ + { + value: 'my_text', + key: '', }, - }, - { - id: 'input_map', - type: 'mapArray', - value: [ - [ - { - value: 'my_text', - key: '', - }, - ], - ], - }, - { - id: 'output_map', - type: 'mapArray', - value: [ - [ - { - value: '', - key: 'my_embedding', - }, - ], - ], - }, + ], ], - type: PROCESSOR_TYPE.ML, - optionalFields: [ - { - id: 'description', - type: 'string', - }, - { - id: 'model_config', - type: 'json', - }, - { - 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', - }, + }, + { + id: 'output_map', + type: 'mapArray', + value: [ + [ + { + value: '', + key: 'my_embedding', + }, + ], ], }, ], + type: PROCESSOR_TYPE.ML, + optionalFields: [ + { + id: 'query_template', + type: 'jsonString', + value: getQueryTemplate(workflowType), + }, + { + id: 'description', + type: 'string', + }, + { + id: 'model_config', + type: 'json', + }, + { + 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', + }, + ], + }; + return { + processors: [mlInferenceProcessor], }; - } else { - return { processors: [] }; } + + return { processors: [] }; } -export function mockStore( - workflowId: string, - workflowName: string, - workflowType: WORKFLOW_TYPE, - includeProcessor: boolean = false -) { - return { - getState: () => ({ - opensearch: { - errorMessage: '', +// Function to get the query template based on workflow type +function getQueryTemplate(workflowType: WORKFLOW_TYPE) { + if (workflowType === WORKFLOW_TYPE.HYBRID_SEARCH) { + return `{ + "_source": { + "excludes": ["my_embedding"] }, - ml: {}, - workflows: { - loading: false, - errorMessage: '', - workflows: { - [workflowId]: generateWorkflow( - workflowId, - workflowName, - workflowType, - includeProcessor - ), - }, + "query": { + "hybrid": { + "queries": [ + { + "match": { + "my_text": { + "query": "{{query_text}}" + } + } + }, + { + "knn": { + "my_embedding": { + "vector": jest.fn(), + "k": 10 + } + } + } + ] + } + } + }`; + } + + if (workflowType === WORKFLOW_TYPE.SEMANTIC_SEARCH) { + return `{ + "_source": { + "excludes": ["my_embedding"] }, - }), - dispatch: jest.fn(), - subscribe: jest.fn(), - replaceReducer: jest.fn(), - [Symbol.observable]: jest.fn(), - }; + "query": { + "knn": { + "my_embedding": { + "vector": jest.fn(), + "k": 10 + } + } + } + }`; + } +} + +function getResponseProcessor(workflowType: WORKFLOW_TYPE): ProcessorsConfig { + return workflowType === WORKFLOW_TYPE.HYBRID_SEARCH + ? { + processors: [ + { + id: 'normalization_processor', + name: 'Normalization Processor', + type: PROCESSOR_TYPE.NORMALIZATION, + fields: [], + optionalFields: [ + { id: 'weights', type: 'string', value: '0.5, 0.5' }, + { + id: 'normalization_technique', + type: 'select', + selectOptions: ['min_max', 'l2'], + }, + { + id: 'combination_technique', + type: 'select', + selectOptions: [ + 'arithmetic_mean', + 'geometric_mean', + 'harmonic_mean', + ], + }, + { id: 'description', type: 'string' }, + { id: 'tag', type: 'string' }, + ], + }, + ], + } + : { processors: [] }; } export const resizeObserverMock = jest.fn().mockImplementation(() => ({ From 8a4d8b223ee1737dac281a5d3bd7a5fc412614de Mon Sep 17 00:00:00 2001 From: saimedhi Date: Thu, 5 Sep 2024 14:23:47 -0700 Subject: [PATCH 6/8] workflow_detail tests Signed-off-by: saimedhi --- .../workflow_detail/workflow_detail.test.tsx | 121 +++++------------- 1 file changed, 34 insertions(+), 87 deletions(-) diff --git a/public/pages/workflow_detail/workflow_detail.test.tsx b/public/pages/workflow_detail/workflow_detail.test.tsx index 2b8fccba..3e8f06f1 100644 --- a/public/pages/workflow_detail/workflow_detail.test.tsx +++ b/public/pages/workflow_detail/workflow_detail.test.tsx @@ -57,94 +57,41 @@ const workflowId = '12345'; const workflowName = 'test_workflow'; describe('WorkflowDetail', () => { - test('renders the page with Custom type', () => { - const { getAllByText, getByText, getByRole } = renderWithRouter( - workflowId, - workflowName, - WORKFLOW_TYPE.CUSTOM - ); + const workflowTypes = [ + WORKFLOW_TYPE.CUSTOM, + WORKFLOW_TYPE.SEMANTIC_SEARCH, + WORKFLOW_TYPE.HYBRID_SEARCH, + ]; - expect(getAllByText(workflowName).length).toBeGreaterThan(0); - expect(getAllByText('Create an ingest pipeline').length).toBeGreaterThan(0); - expect(getAllByText('Skip ingestion pipeline').length).toBeGreaterThan(0); - expect(getAllByText('Define ingest pipeline').length).toBeGreaterThan(0); - expect(getAllByText('Tools').length).toBeGreaterThan(0); - expect(getAllByText('Preview').length).toBeGreaterThan(0); - expect(getAllByText('Not started').length).toBeGreaterThan(0); - expect( - getAllByText((content, element) => content.startsWith('Last updated:')) - .length - ).toBeGreaterThan(0); - expect(getAllByText('Search pipeline').length).toBeGreaterThan(0); - expect(getByText('Close')).toBeInTheDocument(); - expect(getByText('Export')).toBeInTheDocument(); - expect(getByText('Visual')).toBeInTheDocument(); - expect(getByText('JSON')).toBeInTheDocument(); - expect(getByRole('tab', { name: 'Run ingestion' })).toBeInTheDocument(); - expect(getByRole('tab', { name: 'Run queries' })).toBeInTheDocument(); - expect(getByRole('tab', { name: 'Errors' })).toBeInTheDocument(); - expect(getByRole('tab', { name: 'Resources' })).toBeInTheDocument(); - }); -}); - -describe('WorkflowDetail', () => { - test('renders the page with Semantic Search type', () => { - const { getAllByText, getByText, getByRole } = renderWithRouter( - workflowId, - workflowName, - WORKFLOW_TYPE.SEMANTIC_SEARCH - ); - - expect(getAllByText(workflowName).length).toBeGreaterThan(0); - expect(getAllByText('Create an ingest pipeline').length).toBeGreaterThan(0); - expect(getAllByText('Skip ingestion pipeline').length).toBeGreaterThan(0); - expect(getAllByText('Define ingest pipeline').length).toBeGreaterThan(0); - expect(getAllByText('Tools').length).toBeGreaterThan(0); - expect(getAllByText('Preview').length).toBeGreaterThan(0); - expect(getAllByText('Not started').length).toBeGreaterThan(0); - expect( - getAllByText((content, element) => content.startsWith('Last updated:')) - .length - ).toBeGreaterThan(0); - expect(getAllByText('Search pipeline').length).toBeGreaterThan(0); - expect(getByText('Close')).toBeInTheDocument(); - expect(getByText('Export')).toBeInTheDocument(); - expect(getByText('Visual')).toBeInTheDocument(); - expect(getByText('JSON')).toBeInTheDocument(); - expect(getByRole('tab', { name: 'Run ingestion' })).toBeInTheDocument(); - expect(getByRole('tab', { name: 'Run queries' })).toBeInTheDocument(); - expect(getByRole('tab', { name: 'Errors' })).toBeInTheDocument(); - expect(getByRole('tab', { name: 'Resources' })).toBeInTheDocument(); - }); -}); - -describe('WorkflowDetail', () => { - test('renders the page with Hybrid Search type', () => { - const { getAllByText, getByText, getByRole } = renderWithRouter( - workflowId, - workflowName, - WORKFLOW_TYPE.HYBRID_SEARCH - ); + workflowTypes.forEach((type) => { + test(`renders the page with ${type} type`, () => { + const { getAllByText, getByText, getByRole } = renderWithRouter( + workflowId, + workflowName, + type + ); - expect(getAllByText(workflowName).length).toBeGreaterThan(0); - expect(getAllByText('Create an ingest pipeline').length).toBeGreaterThan(0); - expect(getAllByText('Skip ingestion pipeline').length).toBeGreaterThan(0); - expect(getAllByText('Define ingest pipeline').length).toBeGreaterThan(0); - expect(getAllByText('Tools').length).toBeGreaterThan(0); - expect(getAllByText('Preview').length).toBeGreaterThan(0); - expect(getAllByText('Not started').length).toBeGreaterThan(0); - expect( - getAllByText((content, element) => content.startsWith('Last updated:')) - .length - ).toBeGreaterThan(0); - expect(getAllByText('Search pipeline').length).toBeGreaterThan(0); - expect(getByText('Close')).toBeInTheDocument(); - expect(getByText('Export')).toBeInTheDocument(); - expect(getByText('Visual')).toBeInTheDocument(); - expect(getByText('JSON')).toBeInTheDocument(); - expect(getByRole('tab', { name: 'Run ingestion' })).toBeInTheDocument(); - expect(getByRole('tab', { name: 'Run queries' })).toBeInTheDocument(); - expect(getByRole('tab', { name: 'Errors' })).toBeInTheDocument(); - expect(getByRole('tab', { name: 'Resources' })).toBeInTheDocument(); + expect(getAllByText(workflowName).length).toBeGreaterThan(0); + expect(getAllByText('Create an ingest pipeline').length).toBeGreaterThan( + 0 + ); + expect(getAllByText('Skip ingestion pipeline').length).toBeGreaterThan(0); + expect(getAllByText('Define ingest pipeline').length).toBeGreaterThan(0); + expect(getAllByText('Tools').length).toBeGreaterThan(0); + expect(getAllByText('Preview').length).toBeGreaterThan(0); + expect(getAllByText('Not started').length).toBeGreaterThan(0); + expect( + getAllByText((content) => content.startsWith('Last updated:')).length + ).toBeGreaterThan(0); + expect(getAllByText('Search pipeline').length).toBeGreaterThan(0); + expect(getByText('Close')).toBeInTheDocument(); + expect(getByText('Export')).toBeInTheDocument(); + expect(getByText('Visual')).toBeInTheDocument(); + expect(getByText('JSON')).toBeInTheDocument(); + expect(getByRole('tab', { name: 'Run ingestion' })).toBeInTheDocument(); + expect(getByRole('tab', { name: 'Run queries' })).toBeInTheDocument(); + expect(getByRole('tab', { name: 'Errors' })).toBeInTheDocument(); + expect(getByRole('tab', { name: 'Resources' })).toBeInTheDocument(); + }); }); }); From b34a87842743cbcf498535f9747c45cdc3f2ac39 Mon Sep 17 00:00:00 2001 From: saimedhi Date: Fri, 6 Sep 2024 10:51:47 -0700 Subject: [PATCH 7/8] workflow_detail tests Signed-off-by: saimedhi --- .../workflow_detail/workflow_detail.test.tsx | 8 +- public/pages/workflows/new_workflow/utils.ts | 8 +- test/utils.ts | 246 ++---------------- 3 files changed, 33 insertions(+), 229 deletions(-) diff --git a/public/pages/workflow_detail/workflow_detail.test.tsx b/public/pages/workflow_detail/workflow_detail.test.tsx index 3e8f06f1..a023b678 100644 --- a/public/pages/workflow_detail/workflow_detail.test.tsx +++ b/public/pages/workflow_detail/workflow_detail.test.tsx @@ -57,13 +57,7 @@ const workflowId = '12345'; const workflowName = 'test_workflow'; describe('WorkflowDetail', () => { - const workflowTypes = [ - WORKFLOW_TYPE.CUSTOM, - WORKFLOW_TYPE.SEMANTIC_SEARCH, - WORKFLOW_TYPE.HYBRID_SEARCH, - ]; - - workflowTypes.forEach((type) => { + Object.values(WORKFLOW_TYPE).forEach((type) => { test(`renders the page with ${type} type`, () => { const { getAllByText, getByText, getByRole } = renderWithRouter( workflowId, diff --git a/public/pages/workflows/new_workflow/utils.ts b/public/pages/workflows/new_workflow/utils.ts index 8988c739..f6dbb4e6 100644 --- a/public/pages/workflows/new_workflow/utils.ts +++ b/public/pages/workflows/new_workflow/utils.ts @@ -60,7 +60,7 @@ export function enrichPresetWorkflowWithUiMetadata( } as WorkflowTemplate; } -function fetchEmptyMetadata(): UIState { +export function fetchEmptyMetadata(): UIState { return { type: WORKFLOW_TYPE.CUSTOM, config: { @@ -125,7 +125,7 @@ function fetchEmptyMetadata(): UIState { }; } -function fetchSemanticSearchMetadata(): UIState { +export function fetchSemanticSearchMetadata(): UIState { let baseState = fetchEmptyMetadata(); baseState.type = WORKFLOW_TYPE.SEMANTIC_SEARCH; baseState.config.ingest.enrich.processors = [new MLIngestProcessor().toObj()]; @@ -143,7 +143,7 @@ function fetchSemanticSearchMetadata(): UIState { return baseState; } -function fetchMultimodalSearchMetadata(): UIState { +export function fetchMultimodalSearchMetadata(): UIState { let baseState = fetchEmptyMetadata(); baseState.type = WORKFLOW_TYPE.MULTIMODAL_SEARCH; baseState.config.ingest.enrich.processors = [new MLIngestProcessor().toObj()]; @@ -163,7 +163,7 @@ function fetchMultimodalSearchMetadata(): UIState { return baseState; } -function fetchHybridSearchMetadata(): UIState { +export function fetchHybridSearchMetadata(): UIState { let baseState = fetchEmptyMetadata(); baseState.type = WORKFLOW_TYPE.HYBRID_SEARCH; baseState.config.ingest.enrich.processors = [new MLIngestProcessor().toObj()]; diff --git a/test/utils.ts b/test/utils.ts index 8a51fa2a..c0d053c4 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -3,12 +3,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { PROCESSOR_TYPE, WORKFLOW_TYPE } from '../common/constants'; +import { WORKFLOW_TYPE } from '../common/constants'; +import { UIState, Workflow } from '../common/interfaces'; import { - IProcessorConfig, - ProcessorsConfig, - Workflow, -} from '../common/interfaces'; + fetchEmptyMetadata, + fetchHybridSearchMetadata, + fetchMultimodalSearchMetadata, + fetchSemanticSearchMetadata, +} from '../public/pages/workflows/new_workflow/utils'; export function mockStore( workflowId: string, @@ -49,222 +51,30 @@ function generateWorkflow( id: workflowId, name: workflowName, version: { template: '1.0.0', compatibility: ['2.17.0', '3.0.0'] }, - ui_metadata: { - type: workflowType, - config: { - search: { - pipelineName: { - id: 'pipelineName', - type: 'string', - value: 'search_pipeline', - }, - request: { - id: 'request', - type: 'json', - value: '{\n "query": {\n "match_all": {}\n },\n "size": 1000\n}', - }, - index: { name: { id: 'indexName', type: 'string' } }, - enrichRequest: getRequestProcessor(workflowType), - enrichResponse: getResponseProcessor(workflowType), - }, - ingest: { - pipelineName: { - id: 'pipelineName', - type: 'string', - value: 'ingest_pipeline', - }, - enrich: getRequestProcessor(workflowType), - index: { - settings: { id: 'indexSettings', type: 'json' }, - mappings: { - id: 'indexMappings', - type: 'json', - value: '{\n "properties": {}\n}', - }, - name: { - id: 'indexName', - type: 'string', - value: 'my-new-index', - }, - }, - enabled: { id: 'enabled', type: 'boolean', value: true }, - }, - }, - }, + ui_metadata: getConfig(workflowType), }; } - -function getRequestProcessor(workflowType: WORKFLOW_TYPE): ProcessorsConfig { - if ( - workflowType === WORKFLOW_TYPE.HYBRID_SEARCH || - workflowType === WORKFLOW_TYPE.SEMANTIC_SEARCH - ) { - // TODO: In the code below, only the ml_inference processor has been added. Other processors still need to be included. - const mlInferenceProcessor: IProcessorConfig = { - name: 'ML Inference Processor', - id: 'ml_processor_ingest', - fields: [ - { - id: 'model', - type: 'model', - value: { - id: 'dfMPE5EB8_-RPNi-S0gD', - }, - }, - { - id: 'input_map', - type: 'mapArray', - value: [ - [ - { - value: 'my_text', - key: '', - }, - ], - ], - }, - { - id: 'output_map', - type: 'mapArray', - value: [ - [ - { - value: '', - key: 'my_embedding', - }, - ], - ], - }, - ], - type: PROCESSOR_TYPE.ML, - optionalFields: [ - { - id: 'query_template', - type: 'jsonString', - value: getQueryTemplate(workflowType), - }, - { - id: 'description', - type: 'string', - }, - { - id: 'model_config', - type: 'json', - }, - { - 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', - }, - ], - }; - return { - processors: [mlInferenceProcessor], - }; - } - - return { processors: [] }; -} - -// Function to get the query template based on workflow type -function getQueryTemplate(workflowType: WORKFLOW_TYPE) { - if (workflowType === WORKFLOW_TYPE.HYBRID_SEARCH) { - return `{ - "_source": { - "excludes": ["my_embedding"] - }, - "query": { - "hybrid": { - "queries": [ - { - "match": { - "my_text": { - "query": "{{query_text}}" - } - } - }, - { - "knn": { - "my_embedding": { - "vector": jest.fn(), - "k": 10 - } - } - } - ] - } - } - }`; - } - - if (workflowType === WORKFLOW_TYPE.SEMANTIC_SEARCH) { - return `{ - "_source": { - "excludes": ["my_embedding"] - }, - "query": { - "knn": { - "my_embedding": { - "vector": jest.fn(), - "k": 10 - } - } - } - }`; +function getConfig(workflowType: WORKFLOW_TYPE) { + let uiMetadata = {} as UIState; + switch (workflowType) { + case WORKFLOW_TYPE.SEMANTIC_SEARCH: { + uiMetadata = fetchSemanticSearchMetadata(); + break; + } + case WORKFLOW_TYPE.MULTIMODAL_SEARCH: { + uiMetadata = fetchMultimodalSearchMetadata(); + break; + } + case WORKFLOW_TYPE.HYBRID_SEARCH: { + uiMetadata = fetchHybridSearchMetadata(); + break; + } + default: { + uiMetadata = fetchEmptyMetadata(); + break; + } } -} - -function getResponseProcessor(workflowType: WORKFLOW_TYPE): ProcessorsConfig { - return workflowType === WORKFLOW_TYPE.HYBRID_SEARCH - ? { - processors: [ - { - id: 'normalization_processor', - name: 'Normalization Processor', - type: PROCESSOR_TYPE.NORMALIZATION, - fields: [], - optionalFields: [ - { id: 'weights', type: 'string', value: '0.5, 0.5' }, - { - id: 'normalization_technique', - type: 'select', - selectOptions: ['min_max', 'l2'], - }, - { - id: 'combination_technique', - type: 'select', - selectOptions: [ - 'arithmetic_mean', - 'geometric_mean', - 'harmonic_mean', - ], - }, - { id: 'description', type: 'string' }, - { id: 'tag', type: 'string' }, - ], - }, - ], - } - : { processors: [] }; + return uiMetadata; } export const resizeObserverMock = jest.fn().mockImplementation(() => ({ From bcbbcbf68e83cffc0e90034554f77ac1721a081d Mon Sep 17 00:00:00 2001 From: saimedhi Date: Fri, 6 Sep 2024 12:34:28 -0700 Subject: [PATCH 8/8] workflow_detail tests Signed-off-by: saimedhi --- .../pages/workflow_detail/workflow_detail.test.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/public/pages/workflow_detail/workflow_detail.test.tsx b/public/pages/workflow_detail/workflow_detail.test.tsx index a023b678..070bbba0 100644 --- a/public/pages/workflow_detail/workflow_detail.test.tsx +++ b/public/pages/workflow_detail/workflow_detail.test.tsx @@ -28,7 +28,13 @@ jest.mock('../../services', () => { }; }); -const history = createMemoryHistory(); +const workflowId = '12345'; +const workflowName = 'test_workflow'; + +const history = createMemoryHistory({ + initialEntries: [`/workflow/${workflowId}`], +}); + window.ResizeObserver = resizeObserverMock; const renderWithRouter = ( @@ -46,16 +52,12 @@ const renderWithRouter = ( return ; }} /> - ), }); -const workflowId = '12345'; -const workflowName = 'test_workflow'; - describe('WorkflowDetail', () => { Object.values(WORKFLOW_TYPE).forEach((type) => { test(`renders the page with ${type} type`, () => {