├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── draft-release-notes-config.yml ├── labeler.yml ├── pull_request_template.md └── workflows │ ├── add-untriaged.yml │ ├── backport.yml │ ├── build-and-test.yml │ ├── changelog_verifier.yml │ ├── delete_backport_branch.yml │ ├── draft-release-notes.yml │ ├── labeler.yml │ ├── link-check.yml │ ├── lint.yml │ └── remote-integ-tests-workflow.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .whitesource ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── DEVELOPER_GUIDE.md ├── LICENSE ├── MAINTAINERS.md ├── NOTICE ├── README.md ├── RELEASING.md ├── SECURITY.md ├── babel.config.js ├── codecov.yml ├── common ├── constants.ts ├── index.ts ├── interfaces.ts └── utils.ts ├── documentation ├── images │ ├── advanced-input-ingest-input-schema.png │ ├── advanced-input-ingest.png │ ├── advanced-output-ingest-output-schema.png │ ├── advanced-output-ingest.png │ ├── build-and-run-ingestion-response.png │ ├── buttons.png │ ├── edit-query-term.png │ ├── enrich-data.png │ ├── enrich-query-request.png │ ├── enrich-query-results.png │ ├── export-modal.png │ ├── expression-ingest.png │ ├── expression-modal-ingest-validated.png │ ├── expression-modal-ingest.png │ ├── form.png │ ├── import-data-populated.png │ ├── import-data.png │ ├── index-settings-updated.png │ ├── index-settings.png │ ├── input-config-ingest.png │ ├── inspector.png │ ├── ml-config-ingest.png │ ├── output-config-ingest.png │ ├── override-query-with-placeholders.png │ ├── override-query.png │ ├── presets-page.png │ ├── search-response.png │ ├── sidenav.png │ └── workspace.png ├── models.md └── tutorial.md ├── eslintrc.json ├── opensearch_dashboards.json ├── package.json ├── public ├── app.tsx ├── component_types │ ├── base_component.tsx │ ├── index.ts │ ├── indices │ │ ├── base_index.ts │ │ ├── index.ts │ │ └── knn_index.ts │ ├── other │ │ ├── document.tsx │ │ ├── index.ts │ │ ├── search_request.tsx │ │ └── search_response.tsx │ └── transformer │ │ ├── base_transformer.ts │ │ ├── index.ts │ │ └── ml_transformer.ts ├── configs │ ├── base_config.ts │ ├── index.ts │ ├── ingest_processors │ │ ├── copy_ingest_processor.ts │ │ ├── index.ts │ │ ├── ml_ingest_processor.ts │ │ ├── sort_ingest_processor.ts │ │ ├── split_ingest_processor.ts │ │ ├── text_chunking_ingest_processor.ts │ │ ├── text_embedding_ingest_processor.ts │ │ └── text_image_embedding_ingest_processor.ts │ ├── ml_processor.ts │ ├── processor.ts │ ├── search_request_processors │ │ ├── index.ts │ │ └── ml_search_request_processor.ts │ ├── search_response_processors │ │ ├── collapse_processor.ts │ │ ├── index.ts │ │ ├── ml_search_response_processor.ts │ │ ├── normalization_processor.ts │ │ ├── rerank_processor.ts │ │ ├── sort_search_response_processor.ts │ │ └── split_search_response_processor.ts │ ├── sort_processor.ts │ └── split_processor.ts ├── general_components │ ├── general-component-styles.scss │ ├── index.ts │ ├── jsonpath_examples_table.tsx │ ├── multi_select_filter.tsx │ ├── processing_badge.tsx │ ├── processors_title.tsx │ ├── query_params_list.tsx │ ├── results │ │ ├── index.ts │ │ ├── ml_outputs.tsx │ │ ├── results.tsx │ │ ├── results_json.tsx │ │ └── results_table.tsx │ └── service_card │ │ ├── icon.svg │ │ ├── index.ts │ │ └── plugin_card.tsx ├── global-styles.scss ├── index.scss ├── index.ts ├── pages │ ├── index.ts │ ├── workflow_detail │ │ ├── component_input │ │ │ ├── component_input.tsx │ │ │ ├── config_field_list.tsx │ │ │ ├── index.ts │ │ │ ├── ingest_inputs │ │ │ │ ├── advanced_settings.tsx │ │ │ │ ├── bulk_popover_content.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── ingest_data.tsx │ │ │ │ ├── source_data.tsx │ │ │ │ └── source_data_modal.tsx │ │ │ ├── input_fields │ │ │ │ ├── boolean_field.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── json_field.tsx │ │ │ │ ├── json_lines_field.tsx │ │ │ │ ├── map_array_field.tsx │ │ │ │ ├── map_field.tsx │ │ │ │ ├── model_field.tsx │ │ │ │ ├── models_info_popover.tsx │ │ │ │ ├── number_field.tsx │ │ │ │ ├── select_field.tsx │ │ │ │ ├── select_with_custom_options.tsx │ │ │ │ └── text_field.tsx │ │ │ ├── processor_inputs │ │ │ │ ├── index.ts │ │ │ │ ├── ml_processor_inputs │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── ml_processor_inputs.tsx │ │ │ │ │ ├── modals │ │ │ │ │ │ ├── configure_expression_modal.tsx │ │ │ │ │ │ ├── configure_multi_expression_modal.tsx │ │ │ │ │ │ ├── configure_template_modal.tsx │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── override_query_modal.tsx │ │ │ │ │ ├── model_inputs.tsx │ │ │ │ │ ├── model_outputs.tsx │ │ │ │ │ └── utils.ts │ │ │ │ ├── normalization_processor_inputs.tsx │ │ │ │ ├── processor_inputs.tsx │ │ │ │ └── text_chunking_processor_inputs.tsx │ │ │ └── search_inputs │ │ │ │ ├── configure_search_request.tsx │ │ │ │ ├── edit_query_modal.tsx │ │ │ │ ├── index.ts │ │ │ │ └── run_query.tsx │ │ ├── components │ │ │ ├── edit_workflow_metadata_modal.tsx │ │ │ ├── export_modal.tsx │ │ │ ├── header.tsx │ │ │ ├── index.ts │ │ │ └── intro_flyout.tsx │ │ ├── index.ts │ │ ├── left_nav │ │ │ ├── index.ts │ │ │ ├── left_nav.tsx │ │ │ └── nav_content │ │ │ │ ├── down_arrow.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── ingest_content.tsx │ │ │ │ ├── nav_components │ │ │ │ ├── index.ts │ │ │ │ ├── nav_component.tsx │ │ │ │ ├── processor_list.tsx │ │ │ │ └── processors_component.tsx │ │ │ │ └── search_content.tsx │ │ ├── resizable_workspace.tsx │ │ ├── tools │ │ │ ├── errors │ │ │ │ ├── errors.tsx │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── ingest │ │ │ │ ├── index.ts │ │ │ │ └── ingest.tsx │ │ │ ├── query │ │ │ │ ├── index.ts │ │ │ │ └── query.tsx │ │ │ ├── resources │ │ │ │ ├── columns.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── resource_flyout.tsx │ │ │ │ ├── resource_flyout_content.tsx │ │ │ │ ├── resource_list_with_flyout.tsx │ │ │ │ ├── resources.tsx │ │ │ │ └── resources_flyout.tsx │ │ │ └── tools.tsx │ │ ├── workflow-detail-styles.scss │ │ ├── workflow_detail.test.tsx │ │ ├── workflow_detail.tsx │ │ └── workspace │ │ │ ├── index.ts │ │ │ ├── reactflow-styles.scss │ │ │ ├── workspace-styles.scss │ │ │ ├── workspace.tsx │ │ │ ├── workspace_components │ │ │ ├── group_component.tsx │ │ │ ├── index.ts │ │ │ ├── ingest_group_component.tsx │ │ │ ├── input_handle.tsx │ │ │ ├── output_handle.tsx │ │ │ ├── search_group_component.tsx │ │ │ ├── utils.ts │ │ │ └── workspace_component.tsx │ │ │ └── workspace_edge │ │ │ ├── deletable-edge-styles.scss │ │ │ ├── deletable_edge.tsx │ │ │ └── index.ts │ └── workflows │ │ ├── empty_list_message.tsx │ │ ├── get_started_accordion.tsx │ │ ├── import_workflow │ │ ├── import_workflow_modal.test.tsx │ │ ├── import_workflow_modal.tsx │ │ └── index.ts │ │ ├── index.ts │ │ ├── new_workflow │ │ ├── index.ts │ │ ├── new_workflow.test.tsx │ │ ├── new_workflow.tsx │ │ ├── quick_configure_modal.tsx │ │ ├── quick_configure_optional_fields.tsx │ │ ├── use_case.tsx │ │ └── utils.ts │ │ ├── workflow_list │ │ ├── columns.tsx │ │ ├── delete_workflow_modal.tsx │ │ ├── index.ts │ │ ├── resource_list.tsx │ │ ├── workflow_list.test.tsx │ │ └── workflow_list.tsx │ │ ├── workflows.test.tsx │ │ └── workflows.tsx ├── plugin.ts ├── render_app.tsx ├── route_service.ts ├── services.ts ├── store │ ├── index.ts │ ├── reducers │ │ ├── errors_reducer.ts │ │ ├── index.ts │ │ ├── ml_reducer.ts │ │ ├── opensearch_reducer.ts │ │ ├── presets_reducer.ts │ │ └── workflows_reducer.ts │ └── store.ts ├── types.ts └── utils │ ├── config_to_form_utils.ts │ ├── config_to_schema_utils.ts │ ├── config_to_template_utils.test.tsx │ ├── config_to_template_utils.ts │ ├── config_to_workspace_utils.ts │ ├── constants.ts │ ├── form_to_config_utils.ts │ ├── form_to_pipeline_utils.ts │ ├── index.ts │ └── utils.tsx ├── release-notes ├── opensearch-flow-framework-dashboards.release-notes-2.19.0.0.md ├── opensearch-flow-framework-dashboards.release-notes-3.0.0.0-alpha1.md ├── opensearch-flow-framework-dashboards.release-notes-3.0.0.0-beta1.md └── opensearch-flow-framework-dashboards.release-notes-3.0.0.0.md ├── server ├── cluster │ ├── core_plugin.ts │ ├── flow_framework_plugin.ts │ ├── index.ts │ └── ml_plugin.ts ├── index.ts ├── plugin.ts ├── resources │ └── templates │ │ ├── custom.json │ │ ├── hybrid_search.json │ │ ├── hybrid_search_with_rag.json │ │ ├── multimodal_search.json │ │ ├── semantic_search.json │ │ └── vector_search_with_rag.json ├── routes │ ├── flow_framework_routes_service.ts │ ├── helpers.ts │ ├── index.ts │ ├── ml_routes_service.ts │ └── opensearch_routes_service.ts ├── types.ts └── utils │ └── helpers.ts ├── test ├── index.ts ├── interfaces.ts ├── jest.config.js ├── mocks │ ├── empty_mock.ts │ ├── html_id_generator.ts │ ├── index.ts │ ├── mock_core_services.ts │ └── uuid_mock.ts └── utils.ts ├── tsconfig.json ├── whitesource.config └── yarn.lock /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @dbwiddis @owaiskazi19 @joshpalis @ohltyler @amitgalitz @jackiehanyang @minalsha @saimedhi -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug report 3 | about: Create a report to help us improve 4 | title: '[BUG]' 5 | labels: 'bug, untriaged' 6 | assignees: '' 7 | --- 8 | 9 | **What is the bug?** 10 | A clear and concise description of the bug. 11 | 12 | **How can one reproduce the bug?** 13 | Steps to reproduce the behavior: 14 | 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **What is the expected behavior?** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **What is your host/environment?** 24 | 25 | - OS: [e.g. iOS] 26 | - Version [e.g. 22] 27 | - Plugins 28 | 29 | **Do you have any screenshots?** 30 | If applicable, add screenshots to help explain your problem. 31 | 32 | **Do you have any additional context?** 33 | Add any other context about the problem. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: OpenSearch Community Support 3 | url: https://discuss.opendistrocommunity.dev/ 4 | about: Please ask and answer questions here. 5 | - name: AWS/Amazon Security 6 | url: https://aws.amazon.com/security/vulnerability-reporting/ 7 | about: Please report security vulnerabilities here. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🎆 Feature request 3 | about: Request a feature in this project 4 | title: '[FEATURE]' 5 | labels: 'enhancement, untriaged' 6 | assignees: '' 7 | --- 8 | 9 | **Is your feature request related to a problem?** 10 | A clear and concise description of what the problem is, e.g. _I'm always frustrated when [...]_ 11 | 12 | **What solution would you like?** 13 | A clear and concise description of what you want to happen. 14 | 15 | **What alternatives have you considered?** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Do you have any additional context?** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.github/draft-release-notes-config.yml: -------------------------------------------------------------------------------- 1 | # The overall template of the release notes 2 | template: | 3 | Compatible with OpenSearch Dashboards (**set version here**). 4 | $CHANGES 5 | 6 | # Setting the formatting and sorting for the release notes body 7 | name-template: Version (set version here) 8 | change-template: '* $TITLE ([#$NUMBER]($URL))' 9 | sort-by: merged_at 10 | sort-direction: ascending 11 | replacers: 12 | - search: '##' 13 | replace: '###' 14 | 15 | # Organizing the tagged PRs into unified OpenSearch categories 16 | categories: 17 | - title: 'Breaking changes' 18 | labels: 19 | - 'breaking change' 20 | - title: 'Features' 21 | labels: 22 | - 'feature' 23 | - title: 'Enhancements' 24 | labels: 25 | - 'enhancement' 26 | - title: 'Bug Fixes' 27 | labels: 28 | - 'bug' 29 | - 'bug fix' 30 | - title: 'Infrastructure' 31 | labels: 32 | - 'infra' 33 | - 'test' 34 | - 'dependencies' 35 | - 'github actions' 36 | - title: 'Documentation' 37 | labels: 38 | - 'documentation' 39 | - title: 'Maintenance' 40 | labels: 41 | - 'version upgrade' 42 | - 'opensearch release' 43 | - title: 'Refactoring' 44 | labels: 45 | - 'refactor' 46 | - 'code quality' 47 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | # Default all changes to backporting to 2.x branch 2 | backport 2.x: 3 | - '*' 4 | - '*/*' 5 | - '*/**/*' 6 | - '*/**/**/*' 7 | - '*/**/**/**/*' 8 | - '*/**/**/**/**/*' 9 | - '*/**/**/**/**/**/*' 10 | - '*/**/**/**/**/**/**/*' 11 | - '*/**/**/**/**/**/**/**/*' 12 | - '*/**/**/**/**/**/**/**/**/*' 13 | - '.github/**/*' 14 | 15 | # Adding "github actions" label to files in .github or its subdirectories 16 | github actions: 17 | - '.github/**/*' 18 | 19 | # Adding "documentation" label to markdown files or updating release notes 20 | documentation: 21 | - '*.md' 22 | - 'release-notes/*' 23 | 24 | # Adding a "dependencies" label when yarn.lock is updated 25 | dependencies: 26 | - 'yarn.lock' -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | [Describe what this change achieves] 4 | 5 | ### Issues Resolved 6 | 7 | [List any issues this PR will resolve] 8 | 9 | ### Check List 10 | 11 | - [ ] Commits are signed per the DCO using `--signoff` 12 | 13 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. 14 | For more information on following Developer Certificate of Origin and signing off your commits, please check [here](https://github.com/opensearch-project/OpenSearch/blob/main/CONTRIBUTING.md#developer-certificate-of-origin). 15 | -------------------------------------------------------------------------------- /.github/workflows/add-untriaged.yml: -------------------------------------------------------------------------------- 1 | name: Apply 'untriaged' label during issue lifecycle 2 | 3 | on: 4 | issues: 5 | types: [opened, reopened, transferred] 6 | 7 | jobs: 8 | apply-label: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/github-script@v6 12 | with: 13 | script: | 14 | github.rest.issues.addLabels({ 15 | issue_number: context.issue.number, 16 | owner: context.repo.owner, 17 | repo: context.repo.repo, 18 | labels: ['untriaged'] 19 | }) 20 | -------------------------------------------------------------------------------- /.github/workflows/backport.yml: -------------------------------------------------------------------------------- 1 | name: Backport 2 | on: 3 | pull_request_target: 4 | types: 5 | - closed 6 | - labeled 7 | 8 | jobs: 9 | backport: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: write 13 | pull-requests: write 14 | name: Backport 15 | # Only react to merged PRs for security reasons. 16 | # See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target. 17 | if: > 18 | github.event.pull_request.merged 19 | && ( 20 | github.event.action == 'closed' 21 | || ( 22 | github.event.action == 'labeled' 23 | && contains(github.event.label.name, 'backport') 24 | ) 25 | ) 26 | steps: 27 | - name: GitHub App token 28 | id: github_app_token 29 | uses: tibdex/github-app-token@v1.5.0 30 | with: 31 | app_id: ${{ secrets.APP_ID }} 32 | private_key: ${{ secrets.APP_PRIVATE_KEY }} 33 | installation_id: 22958780 34 | 35 | - name: Backport 36 | uses: VachaShah/backport@v2.2.0 37 | with: 38 | github_token: ${{ steps.github_app_token.outputs.token }} 39 | head_template: backport/backport-<%= number %>-to-<%= base %> 40 | labels_template: "<%= JSON.stringify([...labels, 'autocut']) %>" 41 | failure_labels: "failed backport" 42 | -------------------------------------------------------------------------------- /.github/workflows/changelog_verifier.yml: -------------------------------------------------------------------------------- 1 | name: "Changelog Verifier" 2 | on: 3 | push: 4 | branches-ignore: 5 | - 'whitesource-remediate/**' 6 | - 'backport/**' 7 | pull_request: 8 | branches: main 9 | types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] 10 | 11 | jobs: 12 | # Enforces the update of a changelog file on every pull request 13 | verify-changelog: 14 | if: github.repository == 'opensearch-project/dashboards-flow-framework' 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | token: ${{ secrets.GITHUB_TOKEN }} 20 | ref: ${{ github.event.pull_request.head.sha }} 21 | 22 | - uses: dangoslen/changelog-enforcer@v3 23 | with: 24 | skipLabels: "autocut, skip-changelog" 25 | -------------------------------------------------------------------------------- /.github/workflows/delete_backport_branch.yml: -------------------------------------------------------------------------------- 1 | name: Delete merged branch of the backport PRs 2 | on: 3 | pull_request: 4 | types: 5 | - closed 6 | 7 | jobs: 8 | delete-branch: 9 | runs-on: ubuntu-latest 10 | if: startsWith(github.event.pull_request.head.ref,'backport/') 11 | steps: 12 | - name: Delete merged branch 13 | uses: SvanBoxel/delete-merged-branch@main 14 | env: 15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/draft-release-notes.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | update_release_draft: 10 | if: github.repository == 'opensearch-project/dashboards-flow-framework' 11 | name: Update draft release notes 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Update draft release notes 15 | uses: release-drafter/release-drafter@v5 16 | with: 17 | config-name: draft-release-notes-config.yml 18 | name: Version (set here) 19 | tag: (None) 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | name: "Pull Request Labeler" 2 | on: 3 | pull_request_target: 4 | branches: 5 | - main 6 | types: 7 | - opened 8 | 9 | jobs: 10 | label: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: write 14 | pull-requests: write 15 | steps: 16 | - name: GitHub App token 17 | id: github_app_token 18 | uses: tibdex/github-app-token@v1.5.0 19 | with: 20 | app_id: ${{ secrets.APP_ID }} 21 | private_key: ${{ secrets.APP_PRIVATE_KEY }} 22 | installation_id: 22958780 23 | - name: Label 24 | uses: actions/labeler@v4 25 | with: 26 | repo-token: ${{ steps.github_app_token.outputs.token }} -------------------------------------------------------------------------------- /.github/workflows/link-check.yml: -------------------------------------------------------------------------------- 1 | name: Link Checker 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: [main] 7 | 8 | jobs: 9 | linkchecker: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: lychee Link Checker 15 | id: lychee 16 | uses: lycheeverse/lychee-action@master 17 | with: 18 | args: --accept=200,403,429 --exclude=localhost **/*.html **/*.md **/*.txt **/*.json 19 | env: 20 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 21 | - name: Fail if there were link errors 22 | run: exit ${{ steps.lychee.outputs.exit_code }} 23 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Linting workflow 2 | on: [push, pull_request] 3 | env: 4 | OPENSEARCH_DASHBOARDS_VERSION: 'main' 5 | jobs: 6 | run-lint: 7 | name: Run lint script 8 | runs-on: ubuntu-latest 9 | if: github.repository == 'opensearch-project/dashboards-flow-framework' 10 | steps: 11 | - name: Checkout OpenSearch Dashboards 12 | uses: actions/checkout@v2 13 | with: 14 | repository: opensearch-project/OpenSearch-Dashboards 15 | ref: ${{ env.OPENSEARCH_DASHBOARDS_VERSION }} 16 | path: OpenSearch-Dashboards 17 | - name: Setup Node 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version-file: './OpenSearch-Dashboards/.nvmrc' 21 | registry-url: 'https://registry.npmjs.org' 22 | - name: Install Yarn 23 | # Need to use bash to avoid having a windows/linux specific step 24 | shell: bash 25 | run: | 26 | YARN_VERSION=$(node -p "require('./OpenSearch-Dashboards/package.json').engines.yarn") 27 | echo "Installing yarn@$YARN_VERSION" 28 | npm i -g yarn@$YARN_VERSION 29 | - run: node -v 30 | - run: yarn -v 31 | - name: Checkout plugin 32 | uses: actions/checkout@v2 33 | with: 34 | path: OpenSearch-Dashboards/plugins/dashboards-flow-framework 35 | - name: Bootstrap the plugin 36 | run: | 37 | cd OpenSearch-Dashboards/plugins/dashboards-flow-framework 38 | yarn osd bootstrap 39 | - name: Run lint script 40 | run: | 41 | cd OpenSearch-Dashboards/plugins/dashboards-flow-framework 42 | yarn lint:es common/* public/* server/* 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log* 2 | node_modules 3 | /build/ 4 | build 5 | *.iml 6 | *.ipr 7 | *.iws 8 | .idea/ 9 | .DS_Store 10 | *.sublime* 11 | .opensearch-dashboards-plugin-helpers.dev.json 12 | coverage 13 | .vscode 14 | *.code-workspace 15 | offline-module-cache/v2/* 16 | offline-module-cache/v1/* 17 | target 18 | ._.DS_Store 19 | .eslintcache -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.yml 2 | release-notes/ 3 | *.config -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "singleQuote": true, 4 | "printWidth": 80 5 | } 6 | -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | { 2 | "scanSettings": { 3 | "configMode": "AUTO", 4 | "configExternalURL": "", 5 | "projectToken": "", 6 | "baseBranches": [] 7 | }, 8 | "scanSettingsSAST": { 9 | "enableScan": false, 10 | "scanPullRequests": false, 11 | "incrementalScan": true, 12 | "baseBranches": [], 13 | "snippetSize": 10 14 | }, 15 | "checkRunSettings": { 16 | "vulnerableCheckRunConclusionLevel": "failure", 17 | "displayMode": "diff", 18 | "useMendCheckNames": true 19 | }, 20 | "checkRunSettingsSAST": { 21 | "checkRunConclusionLevel": "failure", 22 | "severityThreshold": "high" 23 | }, 24 | "issueSettings": { 25 | "minSeverityLevel": "LOW", 26 | "issueType": "DEPENDENCY" 27 | }, 28 | "remediateSettings": { 29 | "workflowRules": { 30 | "enabled": true 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | All notable changes to this project are documented in this file. 3 | 4 | Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) 5 | 6 | ## [Unreleased 3.0](https://github.com/opensearch-project/anomaly-detection/compare/2.x...HEAD) 7 | ### Features 8 | ### Enhancements 9 | ### Bug Fixes 10 | ### Infrastructure 11 | ### Documentation 12 | ### Maintenance 13 | ### Refactoring 14 | 15 | ## [Unreleased 2.x](https://github.com/opensearch-project/anomaly-detection/compare/2.19...2.x) 16 | ### Features 17 | ### Enhancements 18 | #### Integrate preview panel into inspector panel (https://github.com/opensearch-project/dashboards-flow-framework/pull/720) 19 | ### Bug Fixes 20 | ### Infrastructure 21 | ### Documentation 22 | ### Maintenance 23 | ### Refactoring -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing to this Project 2 | 3 | OpenSearch is a community project that is built and maintained by people just like **you**. 4 | [This document](https://github.com/opensearch-project/.github/blob/main/CONTRIBUTING.md) explains how you can contribute to this and related projects. 5 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | This document contains a list of maintainers in this repo. See [opensearch-project/.github/RESPONSIBILITIES.md](https://github.com/opensearch-project/.github/blob/main/RESPONSIBILITIES.md#maintainer-responsibilities) that explains what the role of maintainer means, what maintainers do in this and other repos, and how they should be doing it. If you're interested in contributing, and becoming a maintainer, see [CONTRIBUTING](CONTRIBUTING.md). 4 | 5 | ## Current Maintainers 6 | 7 | | Maintainer | GitHub ID | Affiliation | 8 | | ------------------------- | ------------------------------------------------- | ----------- | 9 | | Amit Galitzky | [amitgalitz](https://github.com/amitgalitz) | Amazon | 10 | | Jackie Han | [jackiehanyang](https://github.com/jackiehanyang) | Amazon | 11 | | Owais Kazi | [owaiskazi19](https://github.com/owaiskazi19) | Amazon | 12 | | Tyler Ohlsen | [ohltyler](https://github.com/ohltyler) | Amazon | 13 | | Josh Palis | [joshpalis](https://github.com/joshpalis) | Amazon | 14 | | Dan Widdis | [dbwiddis](https://github.com/dbwiddis) | Amazon | 15 | | Minal Shah | [minalsha](https://github.com/minalsha) | Amazon | 16 | | Sai Medhini Reddy Maryada | [saimedhi](https://github.com/saimedhi) | Amazon | 17 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## OpenSearch Flow Framework Dashboards Plugin 2 | 3 | The OpenSearch Flow plugin on OpenSearch Dashboards (OSD) gives users the ability to iteratively build out search and ingest pipelines, initially focusing on ease-of-use for AI/ML-enhanced use cases via [ML inference processors](https://opensearch.org/docs/latest/ingest-pipelines/processors/ml-inference/). Behind the scenes, the plugin uses the [Flow Framework OpenSearch plugin](https://opensearch.org/docs/latest/automating-configurations/index/) for resource management for each use case / workflow a user creates. For example, most use cases involve configuring and creating indices, ingest pipelines, and search pipelines. All of these resources are created, updated, deleted, and maintained by the Flow Framework plugin. When users are satisfied with a use case they have built out, they can export the produced [Workflow Template](https://opensearch.org/docs/latest/automating-configurations/workflow-templates/) to re-create resources for their use cases across different clusters / data sources. 4 | 5 | For tutorials on how to leverage this plugin to build out different AI/ML use cases, see [here](./documentation/.tutorial.md). 6 | 7 | For how to set up this plugin in a development environment, see [DEVELOPER_GUIDE](./DEVELOPER_GUIDE.md). 8 | 9 | ## Security 10 | 11 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 12 | 13 | ## License 14 | 15 | This project is licensed under the Apache-2.0 License. 16 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | This project follows the [OpenSearch release process](https://github.com/opensearch-project/.github/blob/main/RELEASING.md). 2 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Reporting a Vulnerability 2 | 3 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/) or directly via email to aws-security@amazon.com. Please do **not** create a public GitHub issue. 4 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | module.exports = { 7 | presets: [ 8 | require('@babel/preset-env'), 9 | require('@babel/preset-react'), 10 | require('@babel/preset-typescript'), 11 | ], 12 | plugins: [ 13 | ['@babel/plugin-transform-modules-commonjs', { allowTopLevelThis: true }], 14 | [require('@babel/plugin-transform-runtime'), { regenerator: true }], 15 | ], 16 | }; 17 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | # displays different colors depending on below, between, or above the range 3 | range: 50..90 4 | status: 5 | project: 6 | enabled: yes 7 | default: 8 | target: auto 9 | # allows 5% coverage reduction without failing 10 | threshold: 5% 11 | patch: yes 12 | changes: yes 13 | 14 | # disable comments in PRs 15 | comment: yes 16 | -------------------------------------------------------------------------------- /common/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export * from './constants'; 7 | export * from './interfaces'; 8 | export * from './utils'; 9 | -------------------------------------------------------------------------------- /common/utils.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import moment from 'moment'; 7 | import { DATE_FORMAT_PATTERN, WORKFLOW_TYPE } from './'; 8 | import { isEmpty } from 'lodash'; 9 | 10 | export function toFormattedDate(timestampMillis: number): String { 11 | return moment(new Date(timestampMillis)).format(DATE_FORMAT_PATTERN); 12 | } 13 | 14 | const PERMISSIONS_ERROR_PATTERN = /no permissions for \[(.+)\] and User \[name=(.+), backend_roles/; 15 | 16 | export const prettifyErrorMessage = (rawErrorMessage: string) => { 17 | if (isEmpty(rawErrorMessage) || rawErrorMessage === 'undefined') { 18 | return 'Unknown error is returned.'; 19 | } 20 | const match = rawErrorMessage.match(PERMISSIONS_ERROR_PATTERN); 21 | if (isEmpty(match)) { 22 | return rawErrorMessage; 23 | } else { 24 | return `User ${match[2]} has no permissions to [${match[1]}].`; 25 | } 26 | }; 27 | 28 | export function getCharacterLimitedString( 29 | input: string | undefined, 30 | limit: number 31 | ): string { 32 | return input !== undefined 33 | ? input.length > limit 34 | ? input.substring(0, limit - 3) + '...' 35 | : input 36 | : ''; 37 | } 38 | 39 | export function customStringify(jsonObj: {} | []): string { 40 | return JSON.stringify(jsonObj, undefined, 2); 41 | } 42 | 43 | export function customStringifySingleLine(jsonObj: {}): string { 44 | return JSON.stringify(jsonObj, undefined, 0); 45 | } 46 | 47 | export function isVectorSearchUseCase(workflowType?: WORKFLOW_TYPE): boolean { 48 | return ( 49 | workflowType !== undefined && 50 | [ 51 | WORKFLOW_TYPE.SEMANTIC_SEARCH, 52 | WORKFLOW_TYPE.MULTIMODAL_SEARCH, 53 | WORKFLOW_TYPE.HYBRID_SEARCH, 54 | WORKFLOW_TYPE.VECTOR_SEARCH_WITH_RAG, 55 | WORKFLOW_TYPE.HYBRID_SEARCH_WITH_RAG, 56 | ].includes(workflowType) 57 | ); 58 | } 59 | 60 | export function isRAGUseCase(workflowType?: WORKFLOW_TYPE): boolean { 61 | return ( 62 | workflowType !== undefined && 63 | [ 64 | WORKFLOW_TYPE.VECTOR_SEARCH_WITH_RAG, 65 | WORKFLOW_TYPE.HYBRID_SEARCH_WITH_RAG, 66 | ].includes(workflowType) 67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /documentation/images/advanced-input-ingest-input-schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/dashboards-flow-framework/c571664c65f8b4e82573f3f3aa9df4fe874ef11b/documentation/images/advanced-input-ingest-input-schema.png -------------------------------------------------------------------------------- /documentation/images/advanced-input-ingest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/dashboards-flow-framework/c571664c65f8b4e82573f3f3aa9df4fe874ef11b/documentation/images/advanced-input-ingest.png -------------------------------------------------------------------------------- /documentation/images/advanced-output-ingest-output-schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/dashboards-flow-framework/c571664c65f8b4e82573f3f3aa9df4fe874ef11b/documentation/images/advanced-output-ingest-output-schema.png -------------------------------------------------------------------------------- /documentation/images/advanced-output-ingest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/dashboards-flow-framework/c571664c65f8b4e82573f3f3aa9df4fe874ef11b/documentation/images/advanced-output-ingest.png -------------------------------------------------------------------------------- /documentation/images/build-and-run-ingestion-response.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/dashboards-flow-framework/c571664c65f8b4e82573f3f3aa9df4fe874ef11b/documentation/images/build-and-run-ingestion-response.png -------------------------------------------------------------------------------- /documentation/images/buttons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/dashboards-flow-framework/c571664c65f8b4e82573f3f3aa9df4fe874ef11b/documentation/images/buttons.png -------------------------------------------------------------------------------- /documentation/images/edit-query-term.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/dashboards-flow-framework/c571664c65f8b4e82573f3f3aa9df4fe874ef11b/documentation/images/edit-query-term.png -------------------------------------------------------------------------------- /documentation/images/enrich-data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/dashboards-flow-framework/c571664c65f8b4e82573f3f3aa9df4fe874ef11b/documentation/images/enrich-data.png -------------------------------------------------------------------------------- /documentation/images/enrich-query-request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/dashboards-flow-framework/c571664c65f8b4e82573f3f3aa9df4fe874ef11b/documentation/images/enrich-query-request.png -------------------------------------------------------------------------------- /documentation/images/enrich-query-results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/dashboards-flow-framework/c571664c65f8b4e82573f3f3aa9df4fe874ef11b/documentation/images/enrich-query-results.png -------------------------------------------------------------------------------- /documentation/images/export-modal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/dashboards-flow-framework/c571664c65f8b4e82573f3f3aa9df4fe874ef11b/documentation/images/export-modal.png -------------------------------------------------------------------------------- /documentation/images/expression-ingest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/dashboards-flow-framework/c571664c65f8b4e82573f3f3aa9df4fe874ef11b/documentation/images/expression-ingest.png -------------------------------------------------------------------------------- /documentation/images/expression-modal-ingest-validated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/dashboards-flow-framework/c571664c65f8b4e82573f3f3aa9df4fe874ef11b/documentation/images/expression-modal-ingest-validated.png -------------------------------------------------------------------------------- /documentation/images/expression-modal-ingest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/dashboards-flow-framework/c571664c65f8b4e82573f3f3aa9df4fe874ef11b/documentation/images/expression-modal-ingest.png -------------------------------------------------------------------------------- /documentation/images/form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/dashboards-flow-framework/c571664c65f8b4e82573f3f3aa9df4fe874ef11b/documentation/images/form.png -------------------------------------------------------------------------------- /documentation/images/import-data-populated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/dashboards-flow-framework/c571664c65f8b4e82573f3f3aa9df4fe874ef11b/documentation/images/import-data-populated.png -------------------------------------------------------------------------------- /documentation/images/import-data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/dashboards-flow-framework/c571664c65f8b4e82573f3f3aa9df4fe874ef11b/documentation/images/import-data.png -------------------------------------------------------------------------------- /documentation/images/index-settings-updated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/dashboards-flow-framework/c571664c65f8b4e82573f3f3aa9df4fe874ef11b/documentation/images/index-settings-updated.png -------------------------------------------------------------------------------- /documentation/images/index-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/dashboards-flow-framework/c571664c65f8b4e82573f3f3aa9df4fe874ef11b/documentation/images/index-settings.png -------------------------------------------------------------------------------- /documentation/images/input-config-ingest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/dashboards-flow-framework/c571664c65f8b4e82573f3f3aa9df4fe874ef11b/documentation/images/input-config-ingest.png -------------------------------------------------------------------------------- /documentation/images/inspector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/dashboards-flow-framework/c571664c65f8b4e82573f3f3aa9df4fe874ef11b/documentation/images/inspector.png -------------------------------------------------------------------------------- /documentation/images/ml-config-ingest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/dashboards-flow-framework/c571664c65f8b4e82573f3f3aa9df4fe874ef11b/documentation/images/ml-config-ingest.png -------------------------------------------------------------------------------- /documentation/images/output-config-ingest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/dashboards-flow-framework/c571664c65f8b4e82573f3f3aa9df4fe874ef11b/documentation/images/output-config-ingest.png -------------------------------------------------------------------------------- /documentation/images/override-query-with-placeholders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/dashboards-flow-framework/c571664c65f8b4e82573f3f3aa9df4fe874ef11b/documentation/images/override-query-with-placeholders.png -------------------------------------------------------------------------------- /documentation/images/override-query.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/dashboards-flow-framework/c571664c65f8b4e82573f3f3aa9df4fe874ef11b/documentation/images/override-query.png -------------------------------------------------------------------------------- /documentation/images/presets-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/dashboards-flow-framework/c571664c65f8b4e82573f3f3aa9df4fe874ef11b/documentation/images/presets-page.png -------------------------------------------------------------------------------- /documentation/images/search-response.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/dashboards-flow-framework/c571664c65f8b4e82573f3f3aa9df4fe874ef11b/documentation/images/search-response.png -------------------------------------------------------------------------------- /documentation/images/sidenav.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/dashboards-flow-framework/c571664c65f8b4e82573f3f3aa9df4fe874ef11b/documentation/images/sidenav.png -------------------------------------------------------------------------------- /documentation/images/workspace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensearch-project/dashboards-flow-framework/c571664c65f8b4e82573f3f3aa9df4fe874ef11b/documentation/images/workspace.png -------------------------------------------------------------------------------- /eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "@typescript-eslint/consistent-type-definitions": "off", 4 | "@typescript-eslint/no-empty-interface": "off", 5 | "react-hooks/exhaustive-deps": "off" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /opensearch_dashboards.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "flowFrameworkDashboards", 3 | "version": "3.1.0.0", 4 | "opensearchDashboardsVersion": "3.1.0", 5 | "server": true, 6 | "ui": true, 7 | "requiredBundles": [], 8 | "requiredPlugins": [ 9 | "navigation", 10 | "opensearchDashboardsUtils" 11 | ], 12 | "optionalPlugins": [ 13 | "dataSource", 14 | "dataSourceManagement", 15 | "contentManagement" 16 | ], 17 | "supportedOSDataSourceVersions": ">=2.17.0 <4.0.0", 18 | "requiredOSDataSourcePlugins": [ 19 | "opensearch-ml", 20 | "opensearch-flow-framework" 21 | ], 22 | "configPath": [ 23 | "flowFrameworkDashboards" 24 | ] 25 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dashboards-flow-framework", 3 | "version": "3.1.0.0", 4 | "description": "OpenSearch Flow Framework Dashboards Plugin", 5 | "main": "index.js", 6 | "config": { 7 | "plugin_name": "flowFrameworkDashboards", 8 | "plugin_zip_name": "dashboards-flow-framework" 9 | }, 10 | "scripts": { 11 | "plugin-helpers": "../../scripts/use_node ../../scripts/plugin_helpers", 12 | "osd": "../../scripts/use_node ../../scripts/osd", 13 | "opensearch": "../../scripts/use_node ../../scripts/opensearch", 14 | "lint:es": "../../scripts/use_node ../../scripts/eslint -c eslintrc.json", 15 | "test:jest": "../../node_modules/.bin/jest --config ./test/jest.config.js", 16 | "build": "yarn plugin-helpers build && echo Renaming artifact to $npm_package_config_plugin_zip_name-$npm_package_version.zip && mv ./build/$npm_package_config_plugin_name*.zip ./build/$npm_package_config_plugin_zip_name-$npm_package_version.zip" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/opensearch-project/dashboards-flow-framework.git" 21 | }, 22 | "lint-staged": { 23 | "*.{ts,tsx,js,jsx,json,css,md}": [ 24 | "prettier --write", 25 | "git add" 26 | ] 27 | }, 28 | "dependencies": { 29 | "@types/jsonpath": "^0.2.4", 30 | "flattie": "^1.1.1", 31 | "formik": "2.4.2", 32 | "jsonpath": "^1.1.1", 33 | "reactflow": "^11.8.3", 34 | "yup": "^1.3.2" 35 | }, 36 | "devDependencies": {}, 37 | "resolutions": {} 38 | } -------------------------------------------------------------------------------- /public/app.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react'; 7 | import { 8 | Route, 9 | RouteComponentProps, 10 | Switch, 11 | } from 'react-router-dom'; 12 | import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; 13 | import { APP_PATH } from './utils'; 14 | import { 15 | Workflows, 16 | WorkflowDetail, 17 | WorkflowDetailRouterProps, 18 | WorkflowsRouterProps, 19 | } from './pages'; 20 | import { MountPoint } from '../../../src/core/public'; 21 | 22 | // styling 23 | import './global-styles.scss'; 24 | 25 | interface Props extends RouteComponentProps { 26 | setHeaderActionMenu: (menuMount?: MountPoint) => void; 27 | } 28 | 29 | export const FlowFrameworkDashboardsApp = (props: Props) => { 30 | const { setHeaderActionMenu } = props; 31 | 32 | // Render the application DOM. 33 | return ( 34 | 39 | 40 | 41 | 45 | ) => ( 46 | 50 | )} 51 | /> 52 | ) => ( 55 | 56 | )} 57 | /> 58 | {/* 59 | Defaulting to Workflows page. The pathname will need to be updated 60 | to handle the redirection and get the router props consistent. 61 | */} 62 | ) => { 65 | if (props.history.location.pathname !== APP_PATH.WORKFLOWS) { 66 | props.history.replace({ 67 | ...history, 68 | pathname: APP_PATH.WORKFLOWS, 69 | }); 70 | } 71 | return ( 72 | 76 | ); 77 | }} 78 | /> 79 | 80 | 81 | 82 | ); 83 | }; 84 | -------------------------------------------------------------------------------- /public/component_types/base_component.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { 7 | COMPONENT_CLASS, 8 | IComponent, 9 | IComponentInput, 10 | IComponentOutput, 11 | } from '../../common'; 12 | 13 | /** 14 | * A base UI drag-and-drop component class. 15 | */ 16 | export abstract class BaseComponent implements IComponent { 17 | type: COMPONENT_CLASS; 18 | label: string; 19 | description: string; 20 | inputs?: IComponentInput[]; 21 | outputs?: IComponentOutput[]; 22 | 23 | // No-op constructor. If there are general / defaults for field values, add in here. 24 | constructor() { 25 | this.inputs = []; 26 | this.outputs = []; 27 | } 28 | 29 | // Persist a standard toObj() fn that all component classes can use. This is necessary 30 | // so we have standard JS Object when serializing comoponent state in redux. 31 | toObj() { 32 | return Object.assign({}, this); 33 | } 34 | 35 | // Helper fn to strip all fields for a component if we want to view it in the UI 36 | // but not have it tied to any form/inputs. Example: showing an Index component in search, 37 | // even if it is provisioned in ingest. 38 | toPlaceholderObj() { 39 | return { 40 | ...Object.assign({}, this), 41 | createFields: [], 42 | fields: [], 43 | }; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /public/component_types/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export * from './transformer'; 7 | export * from './indices'; 8 | export * from './other'; 9 | -------------------------------------------------------------------------------- /public/component_types/indices/base_index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { COMPONENT_CATEGORY, COMPONENT_CLASS } from '../../../common'; 7 | import { BaseComponent } from '../base_component'; 8 | 9 | /** 10 | * A basic index placeholder UI component. Input/output depends on ingest or search context. 11 | * Does not have any functionality. 12 | */ 13 | export class BaseIndex extends BaseComponent { 14 | constructor(category: COMPONENT_CATEGORY) { 15 | super(); 16 | this.type = COMPONENT_CLASS.INDEX; 17 | this.label = 'Index'; 18 | this.description = 19 | category === COMPONENT_CATEGORY.INGEST 20 | ? 'Index for ingesting data' 21 | : 'Retrieval index'; 22 | this.inputs = [ 23 | { 24 | id: 25 | category === COMPONENT_CATEGORY.INGEST 26 | ? 'document' 27 | : 'search_request', 28 | label: category === COMPONENT_CATEGORY.INGEST ? '' : 'Search Request', 29 | acceptMultiple: false, 30 | }, 31 | ]; 32 | this.outputs = 33 | category === COMPONENT_CATEGORY.INGEST 34 | ? [] 35 | : [ 36 | { 37 | id: 'search_response', 38 | label: 'Search Response', 39 | }, 40 | ]; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /public/component_types/indices/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export * from './base_index'; 7 | export * from './knn_index'; 8 | -------------------------------------------------------------------------------- /public/component_types/indices/knn_index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { COMPONENT_CATEGORY, COMPONENT_CLASS } from '../../../common'; 7 | import { BaseIndex } from './base_index'; 8 | 9 | /** 10 | * A basic knn index placeholder UI component. Input/output depends on ingest or search context. 11 | * Does not have any functionality. 12 | */ 13 | export class KnnIndex extends BaseIndex { 14 | constructor(category: COMPONENT_CATEGORY) { 15 | super(category); 16 | this.type = COMPONENT_CLASS.KNN_INDEX; 17 | this.label = 'k-NN Index'; 18 | this.description = 'A specialized k-NN index'; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /public/component_types/other/document.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { COMPONENT_CLASS } from '../../../common'; 7 | import { BaseComponent } from '../base_component'; 8 | 9 | /** 10 | * A basic document placeholder UI component. 11 | * Does not have any functionality. 12 | */ 13 | export class Document extends BaseComponent { 14 | constructor() { 15 | super(); 16 | this.type = COMPONENT_CLASS.DOCUMENT; 17 | this.label = 'Document'; 18 | this.outputs = [ 19 | { 20 | id: 'document', 21 | label: '', 22 | }, 23 | ]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /public/component_types/other/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export * from './document'; 7 | export * from './search_response'; 8 | export * from './search_request'; 9 | -------------------------------------------------------------------------------- /public/component_types/other/search_request.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { COMPONENT_CLASS } from '../../../common'; 7 | import { BaseComponent } from '../base_component'; 8 | import { isKnnIndex, isKnnQuery } from '../../utils/utils'; 9 | 10 | /** 11 | * A basic search request placeholder UI component. 12 | * Does not have any functionality. 13 | */ 14 | export class SearchRequest extends BaseComponent { 15 | constructor() { 16 | super(); 17 | this.type = COMPONENT_CLASS.SEARCH_REQUEST; 18 | this.label = 'Query'; 19 | this.description = 'Search request'; 20 | this.outputs = [ 21 | { 22 | id: 'search_request', 23 | label: '', 24 | }, 25 | ]; 26 | } 27 | 28 | /** 29 | * Validates if the index supports Knn if it is Knn query 30 | * @param query The current search query 31 | * @param indexSettings The settings of the selected index 32 | * @returns Object containing validation result and warning message if needed 33 | */ 34 | validateKnnQueryToHaveValidKnnIndex( 35 | query: string, 36 | indexSettings: string 37 | ): { 38 | isValid: boolean; 39 | warningMessage?: string; 40 | } { 41 | try { 42 | const queryString = 43 | typeof query === 'string' ? query : JSON.stringify(query); 44 | const hasKnnQuery = isKnnQuery(queryString); 45 | 46 | if (hasKnnQuery) { 47 | const isKnnEnabled = isKnnIndex(indexSettings); 48 | 49 | if (!isKnnEnabled) { 50 | return { 51 | isValid: false, 52 | warningMessage: 53 | 'Warning: The selected index does not have KNN enabled. Please selecte a KNN enabled index.', 54 | }; 55 | } 56 | } 57 | 58 | return { isValid: true }; 59 | } catch (error) { 60 | console.error('Error validating KNN query:', error); 61 | return { isValid: true }; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /public/component_types/other/search_response.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { COMPONENT_CLASS } from '../../../common'; 7 | import { BaseComponent } from '../base_component'; 8 | 9 | /** 10 | * A basic search response placeholder UI component. 11 | * Does not have any functionality. 12 | */ 13 | export class SearchResponse extends BaseComponent { 14 | constructor() { 15 | super(); 16 | this.type = COMPONENT_CLASS.SEARCH_RESPONSE; 17 | this.label = 'Search Results'; 18 | this.inputs = [{ id: 'search_response', label: '', acceptMultiple: false }]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /public/component_types/transformer/base_transformer.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { COMPONENT_CLASS, PROCESSOR_CONTEXT } from '../../../common'; 7 | import { BaseComponent } from '../base_component'; 8 | 9 | /** 10 | * A base transformer UI component representing ingest / search req / search resp processors. 11 | * Input/output descriptions depends on the processor context (ingest, search request, or search response) 12 | */ 13 | export class BaseTransformer extends BaseComponent { 14 | constructor(label: string, description: string, context: PROCESSOR_CONTEXT) { 15 | super(); 16 | this.type = COMPONENT_CLASS.TRANSFORMER; 17 | this.label = label; 18 | this.description = description; 19 | this.inputs = [ 20 | { 21 | id: 22 | context === PROCESSOR_CONTEXT.INGEST 23 | ? 'document' 24 | : context === PROCESSOR_CONTEXT.SEARCH_REQUEST 25 | ? 'search_request' 26 | : 'search_response', 27 | label: 28 | context === PROCESSOR_CONTEXT.INGEST 29 | ? 'Document' 30 | : context === PROCESSOR_CONTEXT.SEARCH_REQUEST 31 | ? 'Search Request' 32 | : 'Search Response', 33 | acceptMultiple: false, 34 | }, 35 | ]; 36 | this.outputs = [ 37 | { 38 | id: 39 | context === PROCESSOR_CONTEXT.INGEST 40 | ? 'document' 41 | : context === PROCESSOR_CONTEXT.SEARCH_REQUEST 42 | ? 'search_request' 43 | : 'search_response', 44 | label: 45 | context === PROCESSOR_CONTEXT.INGEST 46 | ? 'Document' 47 | : context === PROCESSOR_CONTEXT.SEARCH_REQUEST 48 | ? 'Search Request' 49 | : 'Search Response', 50 | }, 51 | ]; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /public/component_types/transformer/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export * from './base_transformer'; 7 | export * from './ml_transformer'; 8 | -------------------------------------------------------------------------------- /public/component_types/transformer/ml_transformer.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { COMPONENT_CLASS, PROCESSOR_CONTEXT } from '../../../common'; 7 | import { BaseTransformer } from './base_transformer'; 8 | 9 | /** 10 | * A base ML transformer UI component representing ML inference processors. 11 | * Input/output descriptions depends on the processor context (ingest, search request, or search response) 12 | */ 13 | export class MLTransformer extends BaseTransformer { 14 | constructor(context: PROCESSOR_CONTEXT) { 15 | super('ML Inference', 'A general ML processor', context); 16 | this.type = COMPONENT_CLASS.ML_TRANSFORMER; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /public/configs/base_config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { IConfig, IConfigField } from '../../common'; 7 | 8 | /** 9 | * A base UI config class. 10 | */ 11 | export abstract class BaseConfig implements IConfig { 12 | id: string; 13 | name: string; 14 | fields: IConfigField[]; 15 | optionalFields?: IConfigField[]; 16 | 17 | // No-op constructor. If there are general / defaults for field values, add in here. 18 | constructor() { 19 | this.id = ''; 20 | this.name = ''; 21 | this.fields = []; 22 | this.optionalFields = []; 23 | } 24 | 25 | // Persist a standard toObj() fn that all component classes can use. This is necessary 26 | // so we have standard JS Object when serializing comoponent state in redux. 27 | toObj() { 28 | return { 29 | id: this.id, 30 | name: this.name, 31 | fields: this.fields, 32 | optionalFields: this.optionalFields, 33 | } as IConfig; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /public/configs/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export * from './ingest_processors'; 7 | export * from './search_request_processors'; 8 | export * from './search_response_processors'; 9 | -------------------------------------------------------------------------------- /public/configs/ingest_processors/copy_ingest_processor.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { PROCESSOR_TYPE } from '../../../common'; 7 | import { generateId } from '../../utils'; 8 | import { Processor } from '../processor'; 9 | 10 | /** 11 | * The copy ingest processor 12 | */ 13 | export class CopyIngestProcessor extends Processor { 14 | constructor() { 15 | super(); 16 | this.name = 'Copy Processor'; 17 | this.type = PROCESSOR_TYPE.COPY; 18 | this.id = generateId('copy_processor_ingest'); 19 | this.fields = [ 20 | { 21 | id: 'source_field', 22 | type: 'string', 23 | }, 24 | { 25 | id: 'target_field', 26 | type: 'string', 27 | }, 28 | ]; 29 | this.optionalFields = [ 30 | { 31 | id: 'ignore_missing', 32 | type: 'boolean', 33 | value: false, 34 | }, 35 | { 36 | id: 'override_target', 37 | type: 'boolean', 38 | value: false, 39 | }, 40 | { 41 | id: 'remove_source', 42 | type: 'boolean', 43 | value: false, 44 | }, 45 | { 46 | id: 'ignore_failure', 47 | type: 'boolean', 48 | value: false, 49 | }, 50 | { 51 | id: 'description', 52 | type: 'textArea', 53 | }, 54 | { 55 | id: 'tag', 56 | type: 'string', 57 | }, 58 | ]; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /public/configs/ingest_processors/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export * from './ml_ingest_processor'; 7 | export * from './split_ingest_processor'; 8 | export * from './sort_ingest_processor'; 9 | export * from './text_chunking_ingest_processor'; 10 | export * from './text_embedding_ingest_processor'; 11 | export * from './text_image_embedding_ingest_processor'; 12 | export * from './copy_ingest_processor'; 13 | -------------------------------------------------------------------------------- /public/configs/ingest_processors/ml_ingest_processor.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { generateId } from '../../utils'; 7 | import { MLProcessor } from '../ml_processor'; 8 | 9 | /** 10 | * The ML processor in the context of ingest 11 | */ 12 | export class MLIngestProcessor extends MLProcessor { 13 | constructor() { 14 | super(); 15 | this.id = generateId('ml_processor_ingest'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /public/configs/ingest_processors/sort_ingest_processor.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { generateId } from '../../utils'; 7 | import { SortProcessor } from '../sort_processor'; 8 | 9 | /** 10 | * The sort processor in the context of ingest 11 | */ 12 | export class SortIngestProcessor extends SortProcessor { 13 | constructor() { 14 | super(); 15 | this.id = generateId('sort_processor_ingest'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /public/configs/ingest_processors/split_ingest_processor.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { generateId } from '../../utils'; 7 | import { SplitProcessor } from '../split_processor'; 8 | 9 | /** 10 | * The split processor in the context of ingest 11 | */ 12 | export class SplitIngestProcessor extends SplitProcessor { 13 | constructor() { 14 | super(); 15 | this.id = generateId('split_processor_ingest'); 16 | this.optionalFields = [ 17 | ...(this.optionalFields || []), 18 | { 19 | id: 'ignore_missing', 20 | type: 'boolean', 21 | value: false, 22 | }, 23 | ]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /public/configs/ingest_processors/text_chunking_ingest_processor.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { PROCESSOR_TYPE, TEXT_CHUNKING_ALGORITHM } from '../../../common'; 7 | import { generateId } from '../../utils'; 8 | import { Processor } from '../processor'; 9 | 10 | /** 11 | * The text chunking ingest processor 12 | */ 13 | export class TextChunkingIngestProcessor extends Processor { 14 | constructor() { 15 | super(); 16 | this.name = 'Text Chunking Processor'; 17 | this.type = PROCESSOR_TYPE.TEXT_CHUNKING; 18 | this.id = generateId('text_chunking_processor_ingest'); 19 | this.fields = [ 20 | { 21 | id: 'field_map', 22 | type: 'map', 23 | }, 24 | { 25 | id: 'algorithm', 26 | type: 'select', 27 | selectOptions: [ 28 | TEXT_CHUNKING_ALGORITHM.FIXED_TOKEN_LENGTH, 29 | TEXT_CHUNKING_ALGORITHM.DELIMITER, 30 | ], 31 | }, 32 | ]; 33 | // optional params include all of those possible from both text chunking algorithms. 34 | // for more details, see https://opensearch.org/docs/latest/ingest-pipelines/processors/text-chunking/ 35 | // the list of optional params per algorithm and shared across algorithms is persisted in 36 | // common/constants.ts 37 | this.optionalFields = [ 38 | // fixed_token_length optional params 39 | { 40 | id: 'token_limit', 41 | type: 'number', 42 | value: 384, 43 | }, 44 | { 45 | id: 'tokenizer', 46 | type: 'string', 47 | value: 'standard', 48 | }, 49 | { 50 | id: 'overlap_rate', 51 | type: 'number', 52 | value: 0, 53 | }, 54 | // delimiter optional params 55 | { 56 | id: 'delimiter', 57 | type: 'string', 58 | }, 59 | // shared optional params (independent of algorithm) 60 | { 61 | id: 'max_chunk_limit', 62 | type: 'number', 63 | value: 100, 64 | }, 65 | { 66 | id: 'description', 67 | type: 'textArea', 68 | }, 69 | { 70 | id: 'tag', 71 | type: 'string', 72 | }, 73 | { 74 | id: 'ignore_missing', 75 | type: 'boolean', 76 | value: false, 77 | }, 78 | ]; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /public/configs/ingest_processors/text_embedding_ingest_processor.ts: -------------------------------------------------------------------------------- 1 | import { PROCESSOR_TYPE } from '../../../common'; 2 | import { generateId } from '../../utils'; 3 | import { Processor } from '../processor'; 4 | 5 | export class TextEmbeddingIngestProcessor extends Processor { 6 | constructor() { 7 | super(); 8 | this.name = 'Text Embedding Processor'; 9 | this.type = PROCESSOR_TYPE.TEXT_EMBEDDING; 10 | this.id = generateId('text_embedding_processor_ingest'); 11 | this.fields = [ 12 | { 13 | id: 'model', 14 | type: 'model', 15 | }, 16 | { 17 | id: 'field_map', 18 | type: 'map', 19 | }, 20 | ]; 21 | this.optionalFields = [ 22 | { 23 | id: 'description', 24 | type: 'textArea', 25 | }, 26 | { 27 | id: 'tag', 28 | type: 'string', 29 | }, 30 | { 31 | id: 'batch_size', 32 | type: 'number', 33 | value: 1, 34 | }, 35 | ]; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /public/configs/ingest_processors/text_image_embedding_ingest_processor.ts: -------------------------------------------------------------------------------- 1 | import { PROCESSOR_TYPE } from '../../../common'; 2 | import { generateId } from '../../utils'; 3 | import { Processor } from '../processor'; 4 | 5 | export class TextImageEmbeddingIngestProcessor extends Processor { 6 | constructor() { 7 | super(); 8 | this.name = 'Text Image Embedding Processor'; 9 | this.type = PROCESSOR_TYPE.TEXT_IMAGE_EMBEDDING; 10 | this.id = generateId('text_image_embedding_processor_ingest'); 11 | this.fields = [ 12 | { 13 | id: 'model', 14 | type: 'model', 15 | }, 16 | { 17 | id: 'embedding', 18 | type: 'string', 19 | }, 20 | { 21 | id: 'field_map', 22 | type: 'map', 23 | }, 24 | ]; 25 | this.optionalFields = [ 26 | { 27 | id: 'description', 28 | type: 'textArea', 29 | }, 30 | { 31 | id: 'tag', 32 | type: 'string', 33 | }, 34 | ]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /public/configs/ml_processor.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { PROCESSOR_TYPE } from '../../common'; 7 | import { Processor } from './processor'; 8 | 9 | /** 10 | * A base ML processor config. Used in ingest and search flows. 11 | * The interfaces are identical across ingest / search request / search response processors. 12 | */ 13 | export abstract class MLProcessor extends Processor { 14 | constructor() { 15 | super(); 16 | this.type = PROCESSOR_TYPE.ML; 17 | this.name = 'ML Inference Processor'; 18 | this.fields = [ 19 | { 20 | id: 'model', 21 | type: 'model', 22 | }, 23 | { 24 | id: 'input_map', 25 | type: 'inputMapArray', 26 | }, 27 | { 28 | id: 'output_map', 29 | type: 'outputMapArray', 30 | }, 31 | ]; 32 | this.optionalFields = [ 33 | { 34 | id: 'model_config', 35 | type: 'json', 36 | }, 37 | { 38 | id: 'full_response_path', 39 | type: 'boolean', 40 | value: false, 41 | }, 42 | { 43 | id: 'ignore_missing', 44 | type: 'boolean', 45 | value: false, 46 | }, 47 | { 48 | id: 'ignore_failure', 49 | type: 'boolean', 50 | value: false, 51 | }, 52 | { 53 | id: 'max_prediction_tasks', 54 | type: 'number', 55 | value: 10, 56 | }, 57 | { 58 | id: 'tag', 59 | type: 'string', 60 | }, 61 | { 62 | id: 'description', 63 | type: 'textArea', 64 | }, 65 | ]; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /public/configs/processor.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { IProcessorConfig, PROCESSOR_TYPE } from '../../common'; 7 | import { BaseConfig } from './base_config'; 8 | 9 | /** 10 | * A base processor config class. 11 | */ 12 | export abstract class Processor extends BaseConfig { 13 | type: PROCESSOR_TYPE; 14 | 15 | // No-op constructor. If there are general / defaults for field values, add in here. 16 | constructor() { 17 | super(); 18 | } 19 | 20 | toObj() { 21 | return { 22 | ...super.toObj(), 23 | type: this.type, 24 | } as IProcessorConfig; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /public/configs/search_request_processors/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export * from './ml_search_request_processor'; 7 | -------------------------------------------------------------------------------- /public/configs/search_request_processors/ml_search_request_processor.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { generateId } from '../../utils'; 7 | import { MLProcessor } from '../ml_processor'; 8 | 9 | /** 10 | * The ML processor in the context of search request 11 | */ 12 | export class MLSearchRequestProcessor extends MLProcessor { 13 | constructor() { 14 | super(); 15 | this.id = generateId('ml_processor_search_request'); 16 | this.optionalFields = [ 17 | { 18 | id: 'query_template', 19 | type: 'jsonString', 20 | }, 21 | ...(this.optionalFields || []), 22 | ]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /public/configs/search_response_processors/collapse_processor.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { PROCESSOR_TYPE } from '../../../common'; 7 | import { Processor } from '../processor'; 8 | import { generateId } from '../../utils'; 9 | 10 | /** 11 | * The collapse processor config. Used in search flows. 12 | */ 13 | export class CollapseProcessor extends Processor { 14 | constructor() { 15 | super(); 16 | this.id = generateId('collapse_processor'); 17 | this.type = PROCESSOR_TYPE.COLLAPSE; 18 | this.name = 'Collapse Processor'; 19 | this.fields = [ 20 | { 21 | id: 'field', 22 | type: 'string', 23 | }, 24 | ]; 25 | this.optionalFields = [ 26 | { 27 | id: 'context_prefix', 28 | type: 'string', 29 | }, 30 | { 31 | id: 'tag', 32 | type: 'string', 33 | }, 34 | { 35 | id: 'description', 36 | type: 'textArea', 37 | }, 38 | { 39 | id: 'ignore_failure', 40 | type: 'boolean', 41 | value: false, 42 | }, 43 | ]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /public/configs/search_response_processors/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export * from './ml_search_response_processor'; 7 | export * from './split_search_response_processor'; 8 | export * from './sort_search_response_processor'; 9 | export * from './normalization_processor'; 10 | export * from './collapse_processor'; 11 | export * from './rerank_processor'; 12 | -------------------------------------------------------------------------------- /public/configs/search_response_processors/ml_search_response_processor.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { generateId } from '../../utils'; 7 | import { MLProcessor } from '../ml_processor'; 8 | 9 | /** 10 | * The ML processor in the context of search response 11 | */ 12 | export class MLSearchResponseProcessor extends MLProcessor { 13 | constructor() { 14 | super(); 15 | this.id = generateId('ml_processor_search_response'); 16 | this.optionalFields = [ 17 | ...(this.optionalFields || []), 18 | { 19 | id: 'one_to_one', 20 | type: 'boolean', 21 | }, 22 | { 23 | id: 'override', 24 | type: 'boolean', 25 | }, 26 | // ext_output is not a field stored in the processor. We expose it as if it is, 27 | // to let users easily toggle saving the output under "ext.ml_inference" or not 28 | { 29 | id: 'ext_output', 30 | type: 'boolean', 31 | value: true, 32 | }, 33 | ]; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /public/configs/search_response_processors/normalization_processor.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { PROCESSOR_TYPE } from '../../../common'; 7 | import { Processor } from '../processor'; 8 | import { generateId } from '../../utils'; 9 | 10 | /** 11 | * The normalization processor config. Used in search flows. 12 | */ 13 | export class NormalizationProcessor extends Processor { 14 | constructor() { 15 | super(); 16 | this.id = generateId('normalization_processor'); 17 | this.type = PROCESSOR_TYPE.NORMALIZATION; 18 | this.name = 'Normalization Processor'; 19 | this.fields = []; 20 | this.optionalFields = [ 21 | { 22 | id: 'weights', 23 | type: 'string', 24 | value: '0.5, 0.5', 25 | }, 26 | { 27 | id: 'normalization_technique', 28 | type: 'select', 29 | selectOptions: ['min_max', 'l2'], 30 | value: 'min_max', 31 | }, 32 | { 33 | id: 'combination_technique', 34 | type: 'select', 35 | selectOptions: ['arithmetic_mean', 'geometric_mean', 'harmonic_mean'], 36 | value: 'arithmetic_mean', 37 | }, 38 | { 39 | id: 'description', 40 | type: 'textArea', 41 | }, 42 | { 43 | id: 'tag', 44 | type: 'string', 45 | }, 46 | ]; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /public/configs/search_response_processors/rerank_processor.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { PROCESSOR_TYPE } from '../../../common'; 7 | import { Processor } from '../processor'; 8 | import { generateId } from '../../utils'; 9 | 10 | /** 11 | * The rerank processor config. Used in search flows. 12 | * For now, only supports the by_field type. For details, see 13 | * https://opensearch.org/docs/latest/search-plugins/search-pipelines/rerank-processor/#the-by_field-rerank-type 14 | */ 15 | export class RerankProcessor extends Processor { 16 | constructor() { 17 | super(); 18 | this.id = generateId('rerank_processor'); 19 | this.type = PROCESSOR_TYPE.RERANK; 20 | this.name = 'Rerank Processor'; 21 | this.fields = [ 22 | { 23 | id: 'target_field', 24 | type: 'string', 25 | }, 26 | ]; 27 | this.optionalFields = [ 28 | { 29 | id: 'remove_target_field', 30 | type: 'boolean', 31 | value: false, 32 | }, 33 | { 34 | id: 'keep_previous_score', 35 | type: 'boolean', 36 | value: false, 37 | }, 38 | { 39 | id: 'tag', 40 | type: 'string', 41 | }, 42 | { 43 | id: 'description', 44 | type: 'textArea', 45 | }, 46 | { 47 | id: 'ignore_failure', 48 | type: 'boolean', 49 | value: false, 50 | }, 51 | ]; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /public/configs/search_response_processors/sort_search_response_processor.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { generateId } from '../../utils'; 7 | import { SortProcessor } from '../sort_processor'; 8 | 9 | /** 10 | * The sort processor in the context of search response 11 | */ 12 | export class SortSearchResponseProcessor extends SortProcessor { 13 | constructor() { 14 | super(); 15 | this.id = generateId('sort_processor_search_response'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /public/configs/search_response_processors/split_search_response_processor.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { generateId } from '../../utils'; 7 | import { SplitProcessor } from '../split_processor'; 8 | 9 | /** 10 | * The split processor in the context of search response 11 | */ 12 | export class SplitSearchResponseProcessor extends SplitProcessor { 13 | constructor() { 14 | super(); 15 | this.id = generateId('split_processor_search_response'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /public/configs/sort_processor.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { PROCESSOR_TYPE, SORT_ORDER } from '../../common'; 7 | import { Processor } from './processor'; 8 | 9 | /** 10 | * A base sort processor config. Used in ingest and search flows. 11 | * The interfaces are identical across ingest / search response processors. 12 | */ 13 | export abstract class SortProcessor extends Processor { 14 | constructor() { 15 | super(); 16 | this.type = PROCESSOR_TYPE.SORT; 17 | this.name = 'Sort Processor'; 18 | this.fields = [ 19 | { 20 | id: 'field', 21 | type: 'string', 22 | }, 23 | ]; 24 | this.optionalFields = [ 25 | { 26 | id: 'order', 27 | type: 'select', 28 | selectOptions: [SORT_ORDER.ASC, SORT_ORDER.DESC], 29 | value: SORT_ORDER.ASC, 30 | }, 31 | { 32 | id: 'target_field', 33 | type: 'string', 34 | }, 35 | { 36 | id: 'description', 37 | type: 'textArea', 38 | }, 39 | { 40 | id: 'tag', 41 | type: 'string', 42 | }, 43 | { 44 | id: 'ignore_failure', 45 | type: 'boolean', 46 | value: false, 47 | }, 48 | ]; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /public/configs/split_processor.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { PROCESSOR_TYPE } from '../../common'; 7 | import { Processor } from './processor'; 8 | 9 | /** 10 | * A base split processor config. Used in ingest and search flows. 11 | * The interfaces are identical across ingest / search response processors. 12 | */ 13 | export abstract class SplitProcessor extends Processor { 14 | constructor() { 15 | super(); 16 | this.type = PROCESSOR_TYPE.SPLIT; 17 | this.name = 'Split Processor'; 18 | this.fields = [ 19 | { 20 | id: 'field', 21 | type: 'string', 22 | }, 23 | { 24 | id: 'separator', 25 | type: 'string', 26 | }, 27 | ]; 28 | this.optionalFields = [ 29 | // TODO: although listed in docs, this field doesn't seem to exist. Fails 30 | // at regular API level. 31 | // { 32 | // id: 'preserve_field', 33 | // type: 'boolean', 34 | // value: false, 35 | // }, 36 | { 37 | id: 'target_field', 38 | type: 'string', 39 | }, 40 | { 41 | id: 'description', 42 | type: 'textArea', 43 | }, 44 | { 45 | id: 'tag', 46 | type: 'string', 47 | }, 48 | { 49 | id: 'ignore_failure', 50 | type: 'boolean', 51 | value: false, 52 | }, 53 | ]; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /public/general_components/general-component-styles.scss: -------------------------------------------------------------------------------- 1 | .multi-select-filter { 2 | &--width { 3 | width: 300px; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /public/general_components/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export { MultiSelectFilter } from './multi_select_filter'; 7 | export { ProcessorsTitle } from './processors_title'; 8 | export { QueryParamsList } from './query_params_list'; 9 | export { JsonPathExamplesTable } from './jsonpath_examples_table'; 10 | export { ProcessingBadge } from './processing_badge'; 11 | export * from './results'; 12 | export * from './service_card'; 13 | -------------------------------------------------------------------------------- /public/general_components/multi_select_filter.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React, { useState } from 'react'; 7 | import { 8 | EuiFilterSelectItem, 9 | EuiFilterGroup, 10 | EuiPopover, 11 | EuiSmallFilterButton, 12 | EuiFlexItem, 13 | } from '@elastic/eui'; 14 | 15 | // styling 16 | import './general-component-styles.scss'; 17 | 18 | interface MultiSelectFilterProps { 19 | title: string; 20 | filters: EuiFilterSelectItem[]; 21 | setSelectedFilters: (filters: EuiFilterSelectItem[]) => void; 22 | } 23 | 24 | /** 25 | * A general multi-select filter. 26 | */ 27 | export function MultiSelectFilter(props: MultiSelectFilterProps) { 28 | const [filters, setFilters] = useState(props.filters); 29 | const [isPopoverOpen, setIsPopoverOpen] = useState(false); 30 | 31 | function onButtonClick() { 32 | setIsPopoverOpen(!isPopoverOpen); 33 | } 34 | function onPopoverClose() { 35 | setIsPopoverOpen(false); 36 | } 37 | 38 | function updateFilter(index: number) { 39 | if (!filters[index]) { 40 | return; 41 | } 42 | const newFilters = [...filters]; 43 | // @ts-ignore 44 | newFilters[index].checked = 45 | // @ts-ignore 46 | newFilters[index].checked === 'on' ? undefined : 'on'; 47 | 48 | setFilters(newFilters); 49 | props.setSelectedFilters( 50 | // @ts-ignore 51 | newFilters.filter((filter) => filter.checked === 'on') 52 | ); 53 | } 54 | 55 | return ( 56 | 57 | 58 | filter.checked === 'on') 68 | } 69 | numActiveFilters={ 70 | // @ts-ignore 71 | filters.filter((filter) => filter.checked === 'on').length 72 | } 73 | > 74 | {props.title} 75 | 76 | } 77 | isOpen={isPopoverOpen} 78 | closePopover={onPopoverClose} 79 | panelPaddingSize="s" 80 | > 81 |
82 | {filters.map((filter, index) => ( 83 | updateFilter(index)} 88 | > 89 | {/* @ts-ignore */} 90 | {filter.name} 91 | 92 | ))} 93 |
94 |
95 |
96 |
97 | ); 98 | } 99 | -------------------------------------------------------------------------------- /public/general_components/processing_badge.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react'; 7 | import { EuiBadge, EuiFlexItem } from '@elastic/eui'; 8 | import { PROCESSOR_CONTEXT } from '../../common'; 9 | 10 | interface ProcessingBadgeProps { 11 | context: PROCESSOR_CONTEXT; 12 | oneToOne: boolean; 13 | } 14 | 15 | /** 16 | * Simple component to display a badge describing how the input data is processed 17 | */ 18 | export function ProcessingBadge(props: ProcessingBadgeProps) { 19 | return ( 20 | <> 21 | {props.context !== PROCESSOR_CONTEXT.SEARCH_REQUEST && ( 22 | 23 | {`${ 24 | props.context === PROCESSOR_CONTEXT.INGEST 25 | ? 'One to one processing' 26 | : props.oneToOne 27 | ? 'One to one processing' 28 | : 'Many to one processing' 29 | }`} 30 | 31 | )} 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /public/general_components/processors_title.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react'; 7 | import { EuiFlexItem, EuiText } from '@elastic/eui'; 8 | 9 | interface ProcessorsTitleProps { 10 | title: string; 11 | processorCount: number; 12 | optional: boolean; 13 | } 14 | 15 | /** 16 | * General component for formatting processor titles 17 | */ 18 | export function ProcessorsTitle(props: ProcessorsTitleProps) { 19 | return ( 20 | 24 | 25 | <> 26 |

{`${props.title} (${props.processorCount})`}

29 |   30 | {props.optional && ( 31 |

32 | - optional 33 |

34 | )} 35 | 36 |
37 |
38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /public/general_components/results/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export { Results } from './results'; 7 | -------------------------------------------------------------------------------- /public/general_components/results/ml_outputs.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react'; 7 | import { isEmpty } from 'lodash'; 8 | import { 9 | EuiCode, 10 | EuiCodeEditor, 11 | EuiEmptyPrompt, 12 | EuiLink, 13 | EuiSpacer, 14 | EuiText, 15 | } from '@elastic/eui'; 16 | import { 17 | customStringify, 18 | ML_RESPONSE_PROCESSOR_EXAMPLE_DOCS_LINK, 19 | } from '../../../common'; 20 | 21 | interface MLOutputsProps { 22 | mlOutputs: {}; 23 | } 24 | 25 | /** 26 | * Small component to render the ML outputs within a raw search response. 27 | */ 28 | export function MLOutputs(props: MLOutputsProps) { 29 | return ( 30 | <> 31 | 32 | {isEmpty(props.mlOutputs) ? ( 33 | No outputs found} titleSize="s" /> 34 | ) : ( 35 | 49 | )} 50 | 51 | 52 | Showing ML outputs stored in ext.ml_inference from 53 | the search response.{' '} 54 | 55 | See an example 56 | 57 | 58 | 59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /public/general_components/results/results.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React, { useEffect, useState } from 'react'; 7 | import { get, isEmpty } from 'lodash'; 8 | import { 9 | EuiPanel, 10 | EuiFlexGroup, 11 | EuiFlexItem, 12 | EuiSmallButtonGroup, 13 | } from '@elastic/eui'; 14 | import { SearchResponse, SimulateIngestPipelineDoc } from '../../../common'; 15 | import { ResultsTable } from './results_table'; 16 | import { ResultsJSON } from './results_json'; 17 | import { MLOutputs } from './ml_outputs'; 18 | 19 | interface ResultsProps { 20 | response: SearchResponse; 21 | } 22 | 23 | enum VIEW { 24 | HITS_TABLE = 'hits_table', 25 | ML_OUTPUTS = 'ml_outputs', 26 | RAW_JSON = 'raw_json', 27 | } 28 | 29 | /** 30 | * Basic component to view OpenSearch response results. Can view hits in a tabular format, 31 | * or the raw JSON response. 32 | */ 33 | export function Results(props: ResultsProps) { 34 | // hits state 35 | const [hits, setHits] = useState([]); 36 | useEffect(() => { 37 | setHits(props.response?.hits?.hits || []); 38 | }, [props.response]); 39 | 40 | // selected view state. auto-navigate to ML outputs if there is values found 41 | // in "ext.ml_inference" in the search response. 42 | const [selectedView, setSelectedView] = useState(VIEW.HITS_TABLE); 43 | useEffect(() => { 44 | if (!isEmpty(get(props.response, 'ext.ml_inference', {}))) { 45 | setSelectedView(VIEW.ML_OUTPUTS); 46 | } 47 | }, [props.response]); 48 | 49 | return ( 50 | 56 | 61 | 62 | setSelectedView(id as VIEW)} 80 | data-testid="resultsToggleButtonGroup" 81 | /> 82 | 83 | 84 | <> 85 | {selectedView === VIEW.HITS_TABLE && } 86 | {selectedView === VIEW.ML_OUTPUTS && ( 87 | 90 | )} 91 | {selectedView === VIEW.RAW_JSON && ( 92 | 93 | )} 94 | 95 | 96 | 97 | 98 | ); 99 | } 100 | 101 | function getMLResponseFromSearchResponse(searchResponse: SearchResponse): {} { 102 | return get(searchResponse, 'ext.ml_inference', {}); 103 | } 104 | -------------------------------------------------------------------------------- /public/general_components/results/results_json.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react'; 7 | import { EuiCodeEditor } from '@elastic/eui'; 8 | import { customStringify, SearchResponse } from '../../../common'; 9 | 10 | interface ResultsJSONProps { 11 | response: SearchResponse; 12 | } 13 | 14 | /** 15 | * Small component to render the raw search response. 16 | */ 17 | export function ResultsJSON(props: ResultsJSONProps) { 18 | return ( 19 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /public/general_components/results/results_table.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React, { useState } from 'react'; 7 | import { 8 | EuiText, 9 | EuiButtonIcon, 10 | RIGHT_ALIGNMENT, 11 | EuiInMemoryTable, 12 | EuiCodeEditor, 13 | EuiPanel, 14 | } from '@elastic/eui'; 15 | import { customStringify, SearchHit } from '../../../common'; 16 | 17 | interface ResultsTableProps { 18 | hits: SearchHit[]; 19 | } 20 | 21 | /** 22 | * Small component to display a list of search results with pagination. 23 | * Can expand each entry to view the full _source response 24 | */ 25 | export function ResultsTable(props: ResultsTableProps) { 26 | const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState<{ 27 | [itemId: string]: any; 28 | }>({}); 29 | 30 | const toggleDetails = (hit: SearchHit) => { 31 | const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap }; 32 | if (itemIdToExpandedRowMapValues[hit._id]) { 33 | delete itemIdToExpandedRowMapValues[hit._id]; 34 | } else { 35 | itemIdToExpandedRowMapValues[hit._id] = ( 36 | 42 | 56 | 57 | ); 58 | } 59 | setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues); 60 | }; 61 | 62 | return ( 63 | { 77 | return ( 78 | 87 | {customStringify(item._source)} 88 | 89 | ); 90 | }, 91 | }, 92 | { 93 | align: RIGHT_ALIGNMENT, 94 | width: '40px', 95 | isExpander: true, 96 | render: (item: SearchHit) => ( 97 | toggleDetails(item)} 99 | aria-label={ 100 | itemIdToExpandedRowMap[item._id] ? 'Collapse' : 'Expand' 101 | } 102 | iconType={ 103 | itemIdToExpandedRowMap[item._id] ? 'arrowUp' : 'arrowDown' 104 | } 105 | /> 106 | ), 107 | }, 108 | ]} 109 | /> 110 | ); 111 | } 112 | -------------------------------------------------------------------------------- /public/general_components/service_card/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export { registerPluginCard } from './plugin_card'; 7 | -------------------------------------------------------------------------------- /public/general_components/service_card/plugin_card.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react'; 7 | import { 8 | EuiSmallButton, 9 | EuiFlexGroup, 10 | EuiFlexItem, 11 | EuiIcon, 12 | EuiTitle, 13 | } from '@elastic/eui'; 14 | import { i18n } from '@osd/i18n'; 15 | import { PLUGIN_ID, PLUGIN_NAME } from '../../../common'; 16 | import { ContentManagementPluginStart } from '../../../../../src/plugins/content_management/public'; 17 | import { CoreStart } from '../../../../../src/core/public'; 18 | import pluginIcon from './icon.svg'; 19 | 20 | const HEADER_TEXT = 'Design and test your search solutions with ease'; 21 | const DESCRIPTION_TEXT = `${PLUGIN_NAME} is a visual editor for creating search AI flows to power advanced search and generative AI solutions.`; 22 | 23 | export const registerPluginCard = ( 24 | contentManagement: ContentManagementPluginStart, 25 | core: CoreStart 26 | ) => { 27 | const icon = ( 28 | 29 | ); 30 | 31 | const footer = ( 32 | 33 | 34 | { 36 | core.application.navigateToApp(PLUGIN_ID); 37 | }} 38 | > 39 | {i18n.translate('flowFrameworkDashboards.opensearchFlowCard.footer', { 40 | defaultMessage: `Try ${PLUGIN_NAME}`, 41 | })} 42 | 43 | 44 | 45 | ); 46 | 47 | contentManagement.registerContentProvider({ 48 | id: 'opensearch_flow_card', 49 | getContent: () => ({ 50 | id: 'opensearch_flow', 51 | kind: 'card', 52 | order: 10, 53 | getTitle: () => { 54 | return ( 55 | 56 | 57 | 58 |

59 | {i18n.translate( 60 | 'flowFrameworkDashboards.opensearchFlowCard.title', 61 | { 62 | defaultMessage: HEADER_TEXT, 63 | } 64 | )} 65 |

66 |
67 |
68 |
69 | ); 70 | }, 71 | description: i18n.translate( 72 | 'flowFrameworkDashboards.opensearchFlowCard.description', 73 | { 74 | defaultMessage: DESCRIPTION_TEXT, 75 | } 76 | ), 77 | getIcon: () => icon, 78 | cardProps: { 79 | children: footer, 80 | layout: 'horizontal', 81 | }, 82 | }), 83 | getTargetArea: () => 'search_overview/config_evaluate_search', 84 | }); 85 | }; 86 | -------------------------------------------------------------------------------- /public/global-styles.scss: -------------------------------------------------------------------------------- 1 | // Maximize space given its relative position 2 | .stretch-relative { 3 | position: relative; 4 | height: 100%; 5 | width: 100%; 6 | } 7 | 8 | // Maximize space given its absolute position 9 | .stretch-absolute { 10 | position: absolute; 11 | height: 100%; 12 | width: 100%; 13 | } 14 | 15 | .configuration-modal { 16 | width: 70vw; 17 | height: 70vh; 18 | } 19 | -------------------------------------------------------------------------------- /public/index.scss: -------------------------------------------------------------------------------- 1 | /* stylelint-disable no-empty-source */ 2 | -------------------------------------------------------------------------------- /public/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import './index.scss'; 7 | 8 | import { FlowFrameworkDashboardsPlugin } from './plugin'; 9 | 10 | // This exports static code and TypeScript types, 11 | // as well as, OpenSearch Dashboards Platform `plugin()` initializer. 12 | export function plugin() { 13 | return new FlowFrameworkDashboardsPlugin(); 14 | } 15 | export { 16 | FlowFrameworkDashboardsPluginSetup, 17 | FlowFrameworkDashboardsPluginStart, 18 | } from './types'; 19 | -------------------------------------------------------------------------------- /public/pages/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export * from './workflows'; 7 | export * from './workflow_detail'; 8 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/component_input/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export * from './input_fields'; 7 | export * from './component_input'; 8 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/component_input/ingest_inputs/bulk_popover_content.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react'; 7 | import { 8 | EuiText, 9 | EuiFlexGroup, 10 | EuiFlexItem, 11 | EuiLink, 12 | EuiCodeBlock, 13 | EuiPopoverTitle, 14 | } from '@elastic/eui'; 15 | import { BULK_API_DOCS_LINK } from '../../../../../common'; 16 | 17 | interface BulkPopoverContentProps { 18 | indexName: string; 19 | } 20 | 21 | /** 22 | * A basic component containing details about the bulk API and link to documentation. 23 | * Provides a partially-complete example, dynamically populated based on an index name. 24 | */ 25 | export function BulkPopoverContent(props: BulkPopoverContentProps) { 26 | return ( 27 | 28 | Ingest additional data 29 | 30 | 31 | 32 | You can ingest additional bulk data into the same index using the 33 | Bulk API.{' '} 34 | 35 | Learn more 36 | 37 | 38 | 39 | 40 | 41 | {`POST ${props.indexName}/_bulk 42 | { "index": { "_index": "${props.indexName}", "_id": //YOUR DOC ID// } } 43 | { //INSERT YOUR DOCUMENTS// }`} 44 | 45 | 46 | 47 | 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/component_input/ingest_inputs/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export * from './ingest_data'; 7 | export * from './source_data'; 8 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/component_input/ingest_inputs/ingest_data.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React, { useState } from 'react'; 7 | import { 8 | EuiCallOut, 9 | EuiFlexGroup, 10 | EuiFlexItem, 11 | EuiLink, 12 | EuiText, 13 | } from '@elastic/eui'; 14 | import { TextField } from '../input_fields'; 15 | import { AdvancedSettings } from './advanced_settings'; 16 | import { KNN_VECTOR_DOCS_LINK } from '../../../../../common'; 17 | 18 | interface IngestDataProps { 19 | disabled: boolean; 20 | } 21 | 22 | /** 23 | * Input component for configuring the data ingest (the OpenSearch index) 24 | */ 25 | export function IngestData(props: IngestDataProps) { 26 | const [hasInvalidDimensions, setHasInvalidDimensions] = useState( 27 | false 28 | ); 29 | 30 | return ( 31 | 32 | {hasInvalidDimensions && ( 33 | 38 | Invalid dimension detected for a vector field mapping. Ensure the 39 | dimension value is set correctly.{' '} 40 | 41 | Learn more 42 | 43 | 44 | } 45 | color="warning" 46 | /> 47 | )} 48 | 49 | 56 | 57 | 58 | 62 | 63 | 64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/component_input/input_fields/boolean_field.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react'; 7 | import { Field, FieldProps } from 'formik'; 8 | import { 9 | EuiCompressedCheckbox, 10 | EuiFlexGroup, 11 | EuiFlexItem, 12 | EuiIconTip, 13 | EuiSwitch, 14 | EuiText, 15 | } from '@elastic/eui'; 16 | 17 | interface BooleanFieldProps { 18 | fieldPath: string; // the full path in string-form to the field (e.g., 'ingest.enrich.processors.text_embedding_processor.inputField') 19 | label: string; 20 | type: ComponentType; 21 | inverse?: boolean; // We may label something as the inverse of how the field is persisted, regarding "on/off" or "true/false" 22 | helpText?: string; 23 | disabled?: boolean; 24 | } 25 | 26 | type ComponentType = 'Checkbox' | 'Switch'; 27 | 28 | /** 29 | * An input field for a boolean value. Implemented as an EuiCompressedRadioGroup with 2 mutually exclusive options. 30 | */ 31 | export function BooleanField(props: BooleanFieldProps) { 32 | return ( 33 | 34 | {({ field, form }: FieldProps) => { 35 | const fieldValue = props.inverse ? !field.value : field.value; 36 | return props.type === 'Checkbox' ? ( 37 | 43 | 44 | 45 | {props.label} 46 | 47 | {props.helpText && ( 48 | 52 | 53 | 54 | )} 55 | 56 | 57 | } 58 | checked={fieldValue === undefined || fieldValue === true} 59 | onChange={() => { 60 | form.setFieldValue(field.name, !field.value); 61 | form.setFieldTouched(field.name, true); 62 | }} 63 | /> 64 | ) : ( 65 | 71 | 72 | 73 | {props.label} 74 | 75 | {props.helpText && ( 76 | 80 | 81 | 82 | )} 83 | 84 | 85 | } 86 | checked={fieldValue === undefined || fieldValue === true} 87 | onChange={() => { 88 | form.setFieldValue(field.name, !field.value); 89 | form.setFieldTouched(field.name, true); 90 | }} 91 | /> 92 | ); 93 | }} 94 | 95 | ); 96 | } 97 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/component_input/input_fields/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export { TextField } from './text_field'; 7 | export { JsonField } from './json_field'; 8 | export { JsonLinesField } from './json_lines_field'; 9 | export { ModelField } from './model_field'; 10 | export { MapField } from './map_field'; 11 | export { MapArrayField } from './map_array_field'; 12 | export { BooleanField } from './boolean_field'; 13 | export { SelectField } from './select_field'; 14 | export { NumberField } from './number_field'; 15 | export { SelectWithCustomOptions } from './select_with_custom_options'; 16 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/component_input/input_fields/number_field.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react'; 7 | import { Field, FieldProps, getIn, useFormikContext } from 'formik'; 8 | import { 9 | EuiCompressedFormRow, 10 | EuiLink, 11 | EuiText, 12 | EuiCompressedFieldNumber, 13 | } from '@elastic/eui'; 14 | import { WorkflowFormValues } from '../../../../../common'; 15 | import { camelCaseToTitleString, getInitialValue } from '../../../../utils'; 16 | 17 | interface NumberFieldProps { 18 | fieldPath: string; // the full path in string-form to the field (e.g., 'ingest.enrich.processors.text_embedding_processor.inputField') 19 | label?: string; 20 | helpLink?: string; 21 | helpText?: string; 22 | placeholder?: string; 23 | showError?: boolean; 24 | disabled?: boolean; 25 | fullWidth?: boolean; 26 | } 27 | 28 | /** 29 | * An input field for a component where users input numbers 30 | */ 31 | export function NumberField(props: NumberFieldProps) { 32 | const { errors, touched } = useFormikContext(); 33 | const disabled = props.disabled ?? false; 34 | 35 | return ( 36 | 37 | {({ field, form }: FieldProps) => { 38 | return ( 39 | 45 | 46 | Learn more 47 | 48 | 49 | ) : undefined 50 | } 51 | helpText={props.helpText || undefined} 52 | error={props.showError && getIn(errors, field.name)} 53 | isInvalid={getIn(errors, field.name) && getIn(touched, field.name)} 54 | fullWidth={props.fullWidth} 55 | > 56 | { 61 | form.setFieldValue(props.fieldPath, e.target.value); 62 | }} 63 | disabled={disabled} 64 | fullWidth={props.fullWidth} 65 | /> 66 | 67 | ); 68 | }} 69 | 70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/component_input/input_fields/select_field.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react'; 7 | import { Field, FieldProps, getIn, useFormikContext } from 'formik'; 8 | import { 9 | EuiCompressedFormRow, 10 | EuiCompressedSuperSelect, 11 | EuiSuperSelectOption, 12 | EuiText, 13 | } from '@elastic/eui'; 14 | import { WorkflowFormValues, IConfigField } from '../../../../../common'; 15 | import { camelCaseToTitleString } from '../../../../utils'; 16 | 17 | interface SelectFieldProps { 18 | field: IConfigField; 19 | fieldPath: string; // the full path in string-form to the field (e.g., 'ingest.enrich.processors.text_embedding_processor.inputField') 20 | onSelectChange?: (option: string) => void; 21 | showInvalid?: boolean; 22 | disabled?: boolean; 23 | fullWidth?: boolean; 24 | } 25 | 26 | /** 27 | * A generic select field from a list of preconfigured options 28 | */ 29 | export function SelectField(props: SelectFieldProps) { 30 | const { errors, touched } = useFormikContext(); 31 | const disabled = props.disabled ?? false; 32 | 33 | return ( 34 | 35 | {({ field, form }: FieldProps) => { 36 | const isInvalid = 37 | (props.showInvalid ?? true) && 38 | getIn(errors, field.name) && 39 | getIn(touched, field.name); 40 | return ( 41 | 46 | 52 | ({ 53 | value: option, 54 | inputDisplay: ( 55 | <> 56 | {option} 57 | 58 | ), 59 | dropdownDisplay: {option}, 60 | disabled: false, 61 | } as EuiSuperSelectOption) 62 | ) 63 | : [] 64 | } 65 | valueOfSelected={field.value || ''} 66 | onChange={(option: string) => { 67 | form.setFieldTouched(props.fieldPath, true); 68 | form.setFieldValue(props.fieldPath, option); 69 | if (props.onSelectChange) { 70 | props.onSelectChange(option); 71 | } 72 | }} 73 | isInvalid={isInvalid} 74 | disabled={disabled} 75 | /> 76 | 77 | ); 78 | }} 79 | 80 | ); 81 | } 82 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/component_input/input_fields/select_with_custom_options.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React, { useEffect, useState } from 'react'; 7 | import { getIn, useFormikContext } from 'formik'; 8 | import { get, isEmpty } from 'lodash'; 9 | import { EuiComboBox, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; 10 | import { WorkflowFormValues } from '../../../../../common'; 11 | 12 | interface SelectWithCustomOptionsProps { 13 | fieldPath: string; 14 | placeholder: string; 15 | options: { label: string }[]; 16 | allowCreate?: boolean; 17 | showInvalid?: boolean; 18 | onChange?: () => void; 19 | disabled?: boolean; 20 | } 21 | 22 | /** 23 | * A generic select field from a list of preconfigured options, and the functionality to add more options 24 | */ 25 | export function SelectWithCustomOptions(props: SelectWithCustomOptionsProps) { 26 | const { 27 | values, 28 | errors, 29 | touched, 30 | setFieldTouched, 31 | setFieldValue, 32 | } = useFormikContext(); 33 | 34 | const isInvalid = 35 | (props.showInvalid ?? true) && 36 | getIn(errors, props.fieldPath) && 37 | getIn(touched, props.fieldPath); 38 | 39 | // selected option state 40 | const [selectedOption, setSelectedOption] = useState([]); 41 | 42 | // set the visible option when the underlying form is updated. 43 | useEffect(() => { 44 | const formValue = getIn(values, props.fieldPath); 45 | if (!isEmpty(formValue)) { 46 | setSelectedOption([{ label: formValue }]); 47 | } else { 48 | setSelectedOption([]); 49 | } 50 | }, [getIn(values, props.fieldPath)]); 51 | 52 | // custom handler when users create a custom option 53 | // only update the form value if non-empty 54 | function onCreateOption(searchValue: any): void { 55 | const normalizedSearchValue = searchValue.trim()?.toLowerCase(); 56 | if (!normalizedSearchValue) { 57 | return; 58 | } 59 | setFieldTouched(props.fieldPath, true); 60 | setFieldValue(props.fieldPath, searchValue); 61 | } 62 | 63 | // custom render fn. 64 | function renderOption(option: any, searchValue: string) { 65 | return ( 66 | 67 | 68 | {option.label || ''} 69 | 70 | {option.type && ( 71 | 72 | 73 | {`(${option.type})`} 74 | 75 | 76 | )} 77 | 78 | ); 79 | } 80 | 81 | return ( 82 | { 92 | setFieldTouched(props.fieldPath, true); 93 | setFieldValue(props.fieldPath, get(options, '0.label')); 94 | if (props.onChange) { 95 | props.onChange(); 96 | } 97 | }} 98 | onCreateOption={props.allowCreate ? onCreateOption : undefined} 99 | customOptionText={ 100 | props.allowCreate ? 'Add {searchValue} as a custom option' : undefined 101 | } 102 | isInvalid={isInvalid} 103 | isDisabled={props.disabled ?? false} 104 | /> 105 | ); 106 | } 107 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/component_input/input_fields/text_field.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react'; 7 | import { Field, FieldProps, getIn, useFormikContext } from 'formik'; 8 | import { 9 | EuiCompressedFieldText, 10 | EuiCompressedFormRow, 11 | EuiCompressedTextArea, 12 | EuiLink, 13 | EuiText, 14 | } from '@elastic/eui'; 15 | import { WorkflowFormValues } from '../../../../../common'; 16 | import { getInitialValue } from '../../../../utils'; 17 | 18 | interface TextFieldProps { 19 | fieldPath: string; // the full path in string-form to the field (e.g., 'ingest.enrich.processors.text_embedding_processor.inputField') 20 | label?: string; 21 | helpLink?: string; 22 | helpText?: string; 23 | placeholder?: string; 24 | showError?: boolean; 25 | showInvalid?: boolean; 26 | fullWidth?: boolean; 27 | textArea?: boolean; 28 | preventWhitespace?: boolean; 29 | disabled?: boolean; 30 | } 31 | 32 | /** 33 | * An input field for a component where users input plaintext 34 | */ 35 | export function TextField(props: TextFieldProps) { 36 | const { errors, touched } = useFormikContext(); 37 | return ( 38 | 39 | {({ field, form }: FieldProps) => { 40 | const isInvalid = 41 | (props.showInvalid ?? true) && 42 | getIn(errors, field.name) && 43 | getIn(touched, field.name); 44 | return ( 45 | 53 | 54 | Learn more 55 | 56 | 57 | ) : undefined 58 | } 59 | helpText={props.helpText || undefined} 60 | error={props.showError && getIn(errors, field.name)} 61 | isInvalid={isInvalid} 62 | > 63 | {props.textArea ? ( 64 | { 70 | form.setFieldValue(props.fieldPath, e.target.value); 71 | }} 72 | isInvalid={isInvalid} 73 | disabled={props.disabled ?? false} 74 | /> 75 | ) : ( 76 | { 82 | form.setFieldValue( 83 | props.fieldPath, 84 | props.preventWhitespace 85 | ? e.target.value?.trim() 86 | : e.target.value 87 | ); 88 | }} 89 | isInvalid={isInvalid} 90 | disabled={props.disabled ?? false} 91 | /> 92 | )} 93 | 94 | ); 95 | }} 96 | 97 | ); 98 | } 99 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/component_input/processor_inputs/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export * from './ml_processor_inputs'; 7 | export * from './processor_inputs'; 8 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/component_input/processor_inputs/ml_processor_inputs/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export * from './ml_processor_inputs'; 7 | export * from './modals'; 8 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/component_input/processor_inputs/ml_processor_inputs/modals/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export * from './override_query_modal'; 7 | export * from './configure_template_modal'; 8 | export * from './configure_expression_modal'; 9 | export * from './configure_multi_expression_modal'; 10 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/component_input/processor_inputs/ml_processor_inputs/utils.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { isEmpty } from 'lodash'; 7 | import { 8 | MapCache, 9 | InputMapEntry, 10 | OutputMapEntry, 11 | Transform, 12 | } from '../../../../../../common'; 13 | 14 | // Update a cache of data transform values based on a given form value 15 | export function updateCache( 16 | cache: MapCache, 17 | mapEntry: InputMapEntry | OutputMapEntry, 18 | idx: number // the mapEntry index 19 | ): MapCache { 20 | const updatedCache = cache; 21 | const curCache = updatedCache[idx]; 22 | if (curCache === undefined || isEmpty(curCache)) { 23 | // case 1: there is no persisted state for this entry index. create a fresh arr 24 | updatedCache[idx] = [mapEntry.value]; 25 | } else if ( 26 | !curCache.some( 27 | (transform: Transform) => 28 | transform.transformType === mapEntry.value.transformType 29 | ) 30 | ) { 31 | // case 2: there is persisted state for this entry index, but not for the particular 32 | // transform type. append to the arr 33 | updatedCache[idx] = [...updatedCache[idx], mapEntry.value]; 34 | } else { 35 | // case 3: there is persisted state for this entry index, and for the particular transform type. 36 | // Update the cache with the current form value(s) 37 | updatedCache[idx] = updatedCache[idx].map((cachedEntry) => { 38 | if (cachedEntry.transformType === mapEntry.value.transformType) { 39 | return mapEntry.value; 40 | } else { 41 | return cachedEntry; 42 | } 43 | }); 44 | } 45 | return updatedCache; 46 | } 47 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/component_input/processor_inputs/normalization_processor_inputs.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react'; 7 | import { EuiAccordion, EuiFlexItem, EuiSpacer } from '@elastic/eui'; 8 | import { 9 | IProcessorConfig, 10 | PROCESSOR_CONTEXT, 11 | WorkflowConfig, 12 | NORMALIZATION_PROCESSOR_LINK, 13 | } from '../../../../../common'; 14 | import { TextField } from '../input_fields'; 15 | import { ConfigFieldList } from '../config_field_list'; 16 | 17 | interface NormalizationProcessorInputsProps { 18 | uiConfig: WorkflowConfig; 19 | config: IProcessorConfig; 20 | baseConfigPath: string; // the base path of the nested config, if applicable. e.g., 'ingest.enrich' 21 | context: PROCESSOR_CONTEXT; 22 | disabled?: boolean; 23 | } 24 | 25 | /** 26 | * Specialized component to render the normalization processor. Adds some helper text around weights field, 27 | * and bubble it up as a primary field to populate (even though it is technically optional). 28 | * In the future, may have a more customizable / guided way for specifying the array of weights. 29 | * For example, could have some visual way of linking it to the underlying sub-queries in the query field, 30 | * enforce its length = the number of queries, etc. 31 | */ 32 | export function NormalizationProcessorInputs( 33 | props: NormalizationProcessorInputsProps 34 | ) { 35 | const disabled = props.disabled ?? false; 36 | // extracting field info from the config 37 | const optionalFields = props.config.optionalFields || []; 38 | const weightsFieldPath = `${props.baseConfigPath}.${props.config.id}.weights`; 39 | const optionalFieldsWithoutWeights = optionalFields.filter( 40 | (field) => field.id !== 'weights' 41 | ); 42 | 43 | return ( 44 | <> 45 | 54 | 55 | 60 | 61 | 62 | 68 | 69 | 70 | 71 | ); 72 | } 73 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/component_input/search_inputs/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export * from './configure_search_request'; 7 | export * from './run_query'; 8 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/components/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export { WorkflowDetailHeader } from './header'; 7 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export * from './workflow_detail'; 7 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/left_nav/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export * from './left_nav'; 7 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/left_nav/nav_content/down_arrow.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react'; 7 | 8 | import { EuiFlexItem, EuiIcon } from '@elastic/eui'; 9 | 10 | interface DownArrowProps { 11 | isDisabled?: boolean; 12 | } 13 | 14 | /** 15 | * The base component for rendering the ingest-related components, including real-time provisioning / error states. 16 | */ 17 | export function DownArrow(props: DownArrowProps) { 18 | return ( 19 | 20 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/left_nav/nav_content/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export * from './ingest_content'; 7 | export * from './search_content'; 8 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/left_nav/nav_content/nav_components/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export * from './nav_component'; 7 | export * from './processors_component'; 8 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/left_nav/nav_content/nav_components/nav_component.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react'; 7 | import { isEmpty } from 'lodash'; 8 | import { 9 | EuiCard, 10 | EuiFlexGroup, 11 | EuiFlexItem, 12 | EuiIcon, 13 | EuiText, 14 | } from '@elastic/eui'; 15 | import { LEFT_NAV_SELECTED_STYLE } from '../../../../../../common'; 16 | 17 | interface NavComponentProps { 18 | title?: string; 19 | description?: string; 20 | icon?: string; 21 | body?: any; 22 | onClick?: () => void; 23 | isDisabled?: boolean; 24 | isSelected?: boolean; 25 | isError?: boolean; 26 | dataTestId?: string; 27 | } 28 | 29 | /** 30 | * General, reusable component used for creating clickable components within 31 | * the LeftNav navigation panel. 32 | */ 33 | export function NavComponent(props: NavComponentProps) { 34 | return ( 35 | 45 | ) : undefined 46 | } 47 | style={props.isSelected ? { border: LEFT_NAV_SELECTED_STYLE } : {}} 48 | titleSize="xs" 49 | title={ 50 | 51 | {!isEmpty(props.title) && ( 52 | 53 | 54 | {props.title} 55 | 56 | 57 | )} 58 | {props.isError && ( 59 | 60 | 61 | 62 | )} 63 | 64 | } 65 | description={ 66 | !isEmpty(props.description) ? ( 67 | 72 | {props.description} 73 | 74 | ) : undefined 75 | } 76 | onClick={props.onClick ?? undefined} 77 | isDisabled={props.isDisabled ?? false} 78 | > 79 | {props.body} 80 | 81 | ); 82 | } 83 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/left_nav/nav_content/nav_components/processors_component.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react'; 7 | import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; 8 | import { NavComponent } from './nav_component'; 9 | import { 10 | CachedFormikState, 11 | PROCESSOR_CONTEXT, 12 | WorkflowConfig, 13 | } from '../../../../../../common'; 14 | import { ProcessorList } from './processor_list'; 15 | 16 | interface ProcessorsComponentProps { 17 | uiConfig: WorkflowConfig; 18 | setUiConfig: (uiConfig: WorkflowConfig) => void; 19 | title: string; 20 | context: PROCESSOR_CONTEXT; 21 | setCachedFormikState: (cachedFormikState: CachedFormikState) => void; 22 | selectedComponentId: string; 23 | setSelectedComponentId: (id: string) => void; 24 | disabled?: boolean; 25 | } 26 | 27 | /** 28 | * The reusable parent nav component containing the list of processors. 29 | * Takes in a "title" and "context" param to determine what processors 30 | * to display (ingest vs. search request vs. search response) 31 | */ 32 | export function ProcessorsComponent(props: ProcessorsComponentProps) { 33 | return ( 34 | 37 | 41 | 42 | 43 | {props.title} 44 | 45 | 46 | 47 |
48 | 57 |
58 | 59 | } 60 | isDisabled={false} 61 | /> 62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/tools/errors/errors.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React, { ReactNode } from 'react'; 7 | import { 8 | EuiCodeBlock, 9 | EuiEmptyPrompt, 10 | EuiFlexItem, 11 | EuiSpacer, 12 | } from '@elastic/eui'; 13 | 14 | interface ErrorsProps { 15 | errorMessages: (string | ReactNode)[]; 16 | } 17 | 18 | /** 19 | * The basic errors component for the Tools panel. 20 | * Displays any errors found while users configure and test their workflow. 21 | */ 22 | export function Errors(props: ErrorsProps) { 23 | return ( 24 | <> 25 | {props.errorMessages?.length === 0 ? ( 26 | No errors} titleSize="s" /> 27 | ) : ( 28 | <> 29 | {props.errorMessages.map((errorMessage, idx) => { 30 | return ( 31 | 32 | 33 | 44 | {errorMessage} 45 | 46 | 47 | ); 48 | })} 49 | 50 | )} 51 | 52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/tools/errors/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export { Errors } from './errors'; 7 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/tools/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export * from './tools'; 7 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/tools/ingest/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export { Ingest } from './ingest'; 7 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/tools/ingest/ingest.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react'; 7 | import { isEmpty } from 'lodash'; 8 | import { EuiCodeEditor, EuiEmptyPrompt, EuiText } from '@elastic/eui'; 9 | 10 | interface IngestProps { 11 | ingestResponse: string; 12 | } 13 | 14 | /** 15 | * The basic ingest component for the Tools panel. 16 | * Displays a read-only view of the ingest response after users perform ingest. 17 | */ 18 | export function Ingest(props: IngestProps) { 19 | return ( 20 | // TODO: known issue with the editor where resizing the resizablecontainer does not 21 | // trigger vertical scroll updates. Updating the window, or reloading the component 22 | // by switching tabs etc. will refresh it correctly 23 | <> 24 | {isEmpty(props.ingestResponse) ? ( 25 | No data} 27 | titleSize="s" 28 | body={ 29 | <> 30 | Run ingest and view the response here. 31 | 32 | } 33 | /> 34 | ) : ( 35 | 49 | )} 50 | 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/tools/query/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export { Query } from './query'; 7 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/tools/resources/columns.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { 7 | WORKFLOW_STEP_TO_RESOURCE_TYPE_MAP, 8 | WORKFLOW_STEP_TYPE, 9 | } from '../../../../../common'; 10 | 11 | export const columns = [ 12 | { 13 | field: 'id', 14 | name: 'ID', 15 | sortable: true, 16 | }, 17 | { 18 | field: 'stepType', 19 | name: 'Type', 20 | sortable: true, 21 | render: (stepType: WORKFLOW_STEP_TYPE) => { 22 | return WORKFLOW_STEP_TO_RESOURCE_TYPE_MAP[stepType]; 23 | }, 24 | }, 25 | ]; 26 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/tools/resources/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export * from './resources'; 7 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/tools/resources/resource_flyout.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react'; 7 | import { 8 | EuiFlyout, 9 | EuiFlyoutBody, 10 | EuiFlyoutHeader, 11 | EuiTitle, 12 | } from '@elastic/eui'; 13 | import { WorkflowResource } from '../../../../../common'; 14 | import { ResourceFlyoutContent } from './resource_flyout_content'; 15 | 16 | interface ResourceFlyoutProps { 17 | resource: WorkflowResource; 18 | resourceDetails: string; 19 | onClose: () => void; 20 | errorMessage?: string; 21 | indexName?: string; 22 | searchPipelineName?: string; 23 | ingestPipelineName?: string; 24 | searchQuery?: string; 25 | } 26 | 27 | /** 28 | * A simple flyout to display details for a particular workflow resource. 29 | */ 30 | export function ResourceFlyout(props: ResourceFlyoutProps) { 31 | return ( 32 | 33 | 34 | 35 |

Resource details

36 |
37 |
38 | 39 | 48 | 49 |
50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/tools/resources/resources.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react'; 7 | import { 8 | EuiEmptyPrompt, 9 | EuiFlexGroup, 10 | EuiFlexItem, 11 | EuiText, 12 | } from '@elastic/eui'; 13 | import { Workflow } from '../../../../../common'; 14 | import { ResourceListWithFlyout } from './resource_list_with_flyout'; 15 | 16 | interface ResourcesProps { 17 | workflow?: Workflow; 18 | } 19 | 20 | /** 21 | * The basic resources component for the Tools panel. Displays all created 22 | * resources for the particular workflow 23 | */ 24 | export function Resources(props: ResourcesProps) { 25 | return ( 26 | <> 27 | {props.workflow?.resourcesCreated && 28 | props.workflow.resourcesCreated.length > 0 ? ( 29 | <> 30 | 31 | 32 | 33 | 34 | 35 | 36 | ) : ( 37 | No resources available} 39 | titleSize="s" 40 | body={ 41 | <> 42 | 43 | Run the pipeline to generate resources. 44 | 45 | 46 | } 47 | /> 48 | )} 49 | 50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/workflow-detail-styles.scss: -------------------------------------------------------------------------------- 1 | .workflow-detail { 2 | overflow: hidden; 3 | } 4 | 5 | .workflow-detail-last-updated { 6 | line-height: $euiFormControlCompressedHeight; 7 | white-space: nowrap; 8 | text-overflow: ellipsis; 9 | overflow: hidden; 10 | } 11 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/workspace/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export { Workspace } from './workspace'; 7 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/workspace/reactflow-styles.scss: -------------------------------------------------------------------------------- 1 | $handle-color: $euiColorLightShade; 2 | $handle-color-valid: $euiColorSuccess; 3 | $handle-color-invalid: $euiColorDanger; 4 | 5 | .reactflow-workspace { 6 | background: $euiColorEmptyShade; 7 | } 8 | 9 | .reactflow-parent-wrapper { 10 | display: flex; 11 | flex-grow: 1; 12 | height: 100%; 13 | } 14 | 15 | .reactflow-parent-wrapper .reactflow-wrapper { 16 | flex-grow: 1; 17 | height: 100%; 18 | } 19 | 20 | .reactflow-workspace .react-flow__node { 21 | width: 250px; 22 | min-height: 25px; 23 | } 24 | 25 | .reactflow__group-node { 26 | width: 1200px; 27 | height: 700px; 28 | border: 'none'; 29 | 30 | &__ingest { 31 | background: rgba($euiColorLightShade, 0.3); 32 | } 33 | &__search { 34 | background: rgba($euiColorLightShade, 0.3); 35 | } 36 | } 37 | 38 | // Overriding the styling for the reactflow node when it is selected. 39 | // We need to use important tag to override ReactFlow's wrapNode that sets the box-shadow. 40 | // Ref: https://github.com/wbkd/react-flow/blob/main/packages/core/src/components/Nodes/wrapNode.tsx#L187 41 | // TODO: when the node sizing is dynamic (e.g., 'min-height', it causes several issues: 42 | // 1. the shadow only covers the min height instead of the node's final rendered height 43 | // 2. the bounding edges of the parent node only fit with the 'min-height' amount, causing 44 | // the node to look like it can be drug slightly out of the parent node 45 | .reactflow-workspace .react-flow__node-custom.selected { 46 | box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.5); 47 | border-radius: 5px; 48 | &:focus { 49 | box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.5) !important; 50 | } 51 | } 52 | 53 | .reactflow-workspace .react-flow__handle { 54 | height: 10px; 55 | width: 10px; 56 | background: $handle-color; 57 | } 58 | 59 | .reactflow-workspace .react-flow__handle-connecting { 60 | background: $handle-color-invalid; 61 | } 62 | 63 | .reactflow-workspace .react-flow__handle-valid { 64 | background: $handle-color-valid; 65 | } 66 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/workspace/workspace-styles.scss: -------------------------------------------------------------------------------- 1 | .workspace-panel { 2 | height: 100%; 3 | } 4 | 5 | .left-nav-static-width { 6 | width: 500px; 7 | } 8 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/workspace/workspace_components/group_component.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react'; 7 | import { EuiFlexGroup, EuiFlexItem, EuiBadge } from '@elastic/eui'; 8 | import { NODE_SPACING, PARENT_NODE_HEIGHT } from '../../../../utils'; 9 | 10 | interface GroupComponentProps { 11 | data: { label: string }; 12 | color: string; 13 | } 14 | 15 | /** 16 | * A simple base component for grouping. 17 | * For more details on resizing, see https://reactflow.dev/examples/nodes/node-resizer 18 | */ 19 | export function GroupComponent(props: GroupComponentProps) { 20 | return ( 21 | 27 | 28 | 29 | {props.data.label} 30 | 31 | 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/workspace/workspace_components/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export { WorkspaceComponent } from './workspace_component'; 7 | export { IngestGroupComponent } from './ingest_group_component'; 8 | export { SearchGroupComponent } from './search_group_component'; 9 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/workspace/workspace_components/ingest_group_component.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react'; 7 | import { GroupComponent } from './group_component'; 8 | 9 | interface IngestGroupComponentProps { 10 | data: { label: string }; 11 | } 12 | 13 | const INGEST_COLOR = '#54B399'; // euiColorVis0: see https://oui.opensearch.org/1.6/#/guidelines/colors 14 | 15 | /** 16 | * A lightweight wrapper on the group component. 17 | * Any specific additions to ingest can be specified here. 18 | */ 19 | export function IngestGroupComponent(props: IngestGroupComponentProps) { 20 | return ; 21 | } 22 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/workspace/workspace_components/input_handle.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React, { useState, useRef, useEffect } from 'react'; 7 | import { Connection, Handle, Position, useReactFlow } from 'reactflow'; 8 | import { isEmpty } from 'lodash'; 9 | import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; 10 | import { IComponent, IComponentInput } from '../../../../../common'; 11 | import { calculateHandlePosition, isValidConnection } from './utils'; 12 | 13 | interface InputHandleProps { 14 | data: IComponent; 15 | input: IComponentInput; 16 | } 17 | 18 | export function InputHandle(props: InputHandleProps) { 19 | const ref = useRef(null); 20 | const reactFlowInstance = useReactFlow(); 21 | const [position, setPosition] = useState(0); 22 | const hasLabel = 23 | props.input.label !== undefined && !isEmpty(props.input.label); 24 | 25 | useEffect(() => { 26 | setPosition(calculateHandlePosition(ref)); 27 | }, [ref]); 28 | 29 | return ( 30 |
31 | <> 32 | {hasLabel && ( 33 | 34 | 35 | {props.input.label} 36 | 37 | 38 | )} 39 | 44 | // @ts-ignore 45 | isValidConnection(connection, reactFlowInstance) 46 | } 47 | style={ 48 | hasLabel 49 | ? { 50 | top: position, 51 | } 52 | : {} 53 | } 54 | /> 55 | 56 |
57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/workspace/workspace_components/output_handle.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React, { useState, useRef, useEffect } from 'react'; 7 | import { Connection, Handle, Position, useReactFlow } from 'reactflow'; 8 | import { isEmpty } from 'lodash'; 9 | import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; 10 | import { IComponent, IComponentOutput } from '../../../../../common'; 11 | import { calculateHandlePosition, isValidConnection } from './utils'; 12 | 13 | interface OutputHandleProps { 14 | data: IComponent; 15 | output: IComponentOutput; 16 | } 17 | 18 | export function OutputHandle(props: OutputHandleProps) { 19 | const ref = useRef(null); 20 | const reactFlowInstance = useReactFlow(); 21 | const [position, setPosition] = useState(0); 22 | const hasLabel = 23 | props.output.label !== undefined && !isEmpty(props.output.label); 24 | 25 | useEffect(() => { 26 | setPosition(calculateHandlePosition(ref)); 27 | }, [ref]); 28 | 29 | return ( 30 |
31 | <> 32 | {hasLabel && ( 33 | 34 | 35 | {props.output.label} 36 | 37 | 38 | )} 39 | 44 | // @ts-ignore 45 | isValidConnection(connection, reactFlowInstance) 46 | } 47 | style={ 48 | hasLabel 49 | ? { 50 | top: position, 51 | } 52 | : {} 53 | } 54 | /> 55 | 56 |
57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/workspace/workspace_components/search_group_component.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react'; 7 | import { GroupComponent } from './group_component'; 8 | 9 | interface SearchGroupComponentProps { 10 | data: { label: string }; 11 | } 12 | const SEARCH_COLOR = '#6092C0'; // euiColorVis1: see https://oui.opensearch.org/1.6/#/guidelines/colors 13 | 14 | /** 15 | * A lightweight wrapper on the group component. 16 | * Any specific additions to search can be specified here. 17 | */ 18 | export function SearchGroupComponent(props: SearchGroupComponentProps) { 19 | return ; 20 | } 21 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/workspace/workspace_components/utils.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { Connection, ReactFlowInstance } from 'reactflow'; 7 | import { IComponentInput } from '../../../../../common'; 8 | 9 | /** 10 | * Collection of utility functions for the workspace component 11 | */ 12 | 13 | // Uses DOM elements to calculate where the handle should be placed 14 | // vertically on the ReactFlow component. offsetTop is the offset relative to the 15 | // parent element, and clientHeight is the element height including padding. 16 | // We can combine them to get the exact amount, in pixels. 17 | export function calculateHandlePosition(ref: any): number { 18 | if (ref.current && ref.current.offsetTop && ref.current.clientHeight) { 19 | return ref.current.offsetTop + ref.current.clientHeight / 2; 20 | } else { 21 | return 0; 22 | } 23 | } 24 | 25 | // Validates that connections can only be made when the source and target classes align, and 26 | // that multiple connections to the same target handle are not allowed unless the input configuration 27 | // for that particular component allows for it. 28 | export function isValidConnection( 29 | connection: Connection, 30 | rfInstance: ReactFlowInstance 31 | ): boolean { 32 | const sourceHandle = connection.sourceHandle; 33 | const targetHandle = connection.targetHandle; 34 | const targetNodeId = connection.target; 35 | 36 | // We store the output classes in a pipe-delimited string. Converting back to a list. 37 | const sourceClasses = sourceHandle?.split('|') || []; 38 | const targetClass = targetHandle || ''; 39 | 40 | if (sourceClasses?.includes(targetClass)) { 41 | const targetNode = rfInstance.getNode(targetNodeId || ''); 42 | if (targetNode) { 43 | const inputConfig = targetNode.data.inputs.find( 44 | (input: IComponentInput) => sourceClasses.includes(input.baseClass) 45 | ) as IComponentInput; 46 | const existingEdge = rfInstance 47 | .getEdges() 48 | .find( 49 | (edge) => 50 | edge.target === targetNodeId && edge.targetHandle === targetHandle 51 | ); 52 | if (existingEdge && inputConfig.acceptMultiple === false) { 53 | return false; 54 | } 55 | } 56 | return true; 57 | } else { 58 | return false; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/workspace/workspace_components/workspace_component.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react'; 7 | import { isEmpty } from 'lodash'; 8 | import { 9 | EuiFlexGroup, 10 | EuiFlexItem, 11 | EuiCard, 12 | EuiText, 13 | EuiSpacer, 14 | EuiIcon, 15 | IconType, 16 | } from '@elastic/eui'; 17 | import { 18 | IComponentData, 19 | IComponentInput, 20 | IComponentOutput, 21 | } from '../../../../../common'; 22 | import { InputHandle } from './input_handle'; 23 | import { OutputHandle } from './output_handle'; 24 | 25 | // styling 26 | import '../../workspace/reactflow-styles.scss'; 27 | 28 | interface WorkspaceComponentProps { 29 | data: IComponentData; 30 | } 31 | 32 | /** 33 | * The React component in the drag-and-drop workspace. It will take in the component data passed 34 | * to it from the workspace and render it appropriately (inputs / params / outputs / etc.). 35 | * As users interact with it (input data, add connections), the stored IComponent data will update. 36 | */ 37 | export function WorkspaceComponent(props: WorkspaceComponentProps) { 38 | const component = props.data; 39 | // we don't render any component body if no metadata, such as no description, or no defined inputs/outputs. 40 | const hasDescription = 41 | component.description !== undefined && !isEmpty(component.description); 42 | const hasIcon = 43 | component.iconType !== undefined && !isEmpty(component.iconType); 44 | const hasMetadata = 45 | hasDescription || 46 | hasLabels(component.inputs) || 47 | hasLabels(component.outputs); 48 | 49 | return ( 50 | 55 | {hasIcon && ( 56 | 57 | 58 | 59 | )} 60 | 61 | 62 |

{component.label}

63 |
64 |
65 | 66 | 67 | } 68 | > 69 | 74 | {hasDescription && ( 75 | 76 | 77 | {component.description} 78 | 79 | 80 | 81 | )} 82 | {component.inputs?.map((input, index) => { 83 | return ( 84 | 85 | 86 | 87 | ); 88 | })} 89 | {component.outputs?.map((output, index) => { 90 | return ( 91 | 92 | 93 | 94 | ); 95 | })} 96 | 97 |
98 | ); 99 | } 100 | 101 | // small utility fn to check if inputs or outputs have labels. Component is dynamically 102 | // rendered based on these being populated or not. 103 | function hasLabels( 104 | inputsOrOutputs: (IComponentInput | IComponentOutput)[] | undefined 105 | ): boolean { 106 | return !isEmpty( 107 | inputsOrOutputs 108 | ?.map((inputOrOutput) => inputOrOutput.label) 109 | .filter((label) => !isEmpty(label)) 110 | ); 111 | } 112 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/workspace/workspace_edge/deletable-edge-styles.scss: -------------------------------------------------------------------------------- 1 | .delete-edge-button { 2 | width: 20px; 3 | height: 20px; 4 | background: #000000; 5 | border: 1px solid #000000; 6 | cursor: pointer; 7 | border-radius: 50%; 8 | font-size: 12px; 9 | line-height: 1; 10 | } 11 | 12 | .delete-edge-button:hover { 13 | box-shadow: 0 0 6px 2px rgba(0, 0, 0, 0.5); 14 | } 15 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/workspace/workspace_edge/deletable_edge.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react'; 7 | import { 8 | BaseEdge, 9 | Edge, 10 | EdgeLabelRenderer, 11 | EdgeProps, 12 | MarkerType, 13 | getBezierPath, 14 | useReactFlow, 15 | } from 'reactflow'; 16 | 17 | // styling 18 | import './deletable-edge-styles.scss'; 19 | 20 | type DeletableEdgeProps = EdgeProps; 21 | 22 | /** 23 | * A custom deletable edge. Renders a delete button in the center of the edge once connected. 24 | * Using bezier path by default. For all edge types, 25 | * see https://reactflow.dev/docs/examples/edges/edge-types/ 26 | */ 27 | export function DeletableEdge(props: DeletableEdgeProps) { 28 | const [edgePath, labelX, labelY] = getBezierPath({ 29 | sourceX: props.sourceX, 30 | sourceY: props.sourceY, 31 | sourcePosition: props.sourcePosition, 32 | targetX: props.targetX, 33 | targetY: props.targetY, 34 | targetPosition: props.targetPosition, 35 | }); 36 | 37 | const reactFlowInstance = useReactFlow(); 38 | 39 | const deleteEdge = (edgeId: string) => { 40 | reactFlowInstance.setEdges( 41 | reactFlowInstance.getEdges().filter((edge: Edge) => edge.id !== edgeId) 42 | ); 43 | }; 44 | 45 | const onEdgeClick = (event: any, edgeId: string) => { 46 | // Prevent this event from bubbling up and putting reactflow into an unexpected state. 47 | // This implementation follows the doc example: https://reactflow.dev/docs/examples/edges/custom-edge/ 48 | event.stopPropagation(); 49 | deleteEdge(edgeId); 50 | }; 51 | 52 | return ( 53 | <> 54 | 55 | 56 | {/** Using in-line styling since scss can't support dynamic values*/} 57 |
67 | 76 |
77 |
78 | 79 | ); 80 | } 81 | -------------------------------------------------------------------------------- /public/pages/workflow_detail/workspace/workspace_edge/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export { DeletableEdge } from './deletable_edge'; 7 | -------------------------------------------------------------------------------- /public/pages/workflows/empty_list_message.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react'; 7 | import { 8 | EuiSmallButton, 9 | EuiFlexGroup, 10 | EuiFlexItem, 11 | EuiSpacer, 12 | EuiText, 13 | } from '@elastic/eui'; 14 | 15 | interface EmptyListMessageProps { 16 | onClickNewWorkflow: () => void; 17 | } 18 | 19 | export function EmptyListMessage(props: EmptyListMessageProps) { 20 | return ( 21 | 22 | 23 | 24 | 25 | 26 | 27 |

No workflows found

28 |
29 |
30 | 31 | 32 | Create a workflow to start building and testing your application. 33 | 34 | 35 | 36 | 37 | New workflow 38 | 39 | 40 |
41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /public/pages/workflows/get_started_accordion.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react'; 7 | import { 8 | EuiFlexGroup, 9 | EuiFlexItem, 10 | EuiSpacer, 11 | EuiText, 12 | EuiCard, 13 | EuiLink, 14 | EuiTitle, 15 | EuiAccordion, 16 | } from '@elastic/eui'; 17 | import { CREATE_WORKFLOW_LINK, ML_REMOTE_MODEL_LINK } from '../../../common'; 18 | 19 | interface GetStartedAccordionProps { 20 | initialIsOpen: boolean; 21 | } 22 | 23 | export function GetStartedAccordion(props: GetStartedAccordionProps) { 24 | return ( 25 | 32 | 33 | Get started 34 | 35 | 36 | } 37 | > 38 | 39 | 40 | 41 | 42 | 46 |

1. Set up models

47 | 48 | } 49 | > 50 | 51 | Connect to an externally hosted model and make it available in 52 | your OpenSearch cluster.{' '} 53 | 54 | Learn more 55 | 56 | 57 |
58 |
59 | 60 | 64 |

2. Ingest data

65 | 66 | } 67 | > 68 | 69 | Import sample data to get started; add processors to customize 70 | your ingest pipeline. 71 | 72 |
73 |
74 | 75 | 79 |

3. Build a search pipeline

80 | 81 | } 82 | > 83 | 84 | Set up a query and configure your search pipeline. 85 | 86 |
87 |
88 | 89 | 93 |

4. Export the workflow

94 | 95 | } 96 | > 97 | 98 | Export your workflow template to create and deploy the workflow 99 | on other OpenSearch clusters.{' '} 100 | 101 | Learn more 102 | 103 | 104 |
105 |
106 |
107 |
108 |
109 | ); 110 | } 111 | -------------------------------------------------------------------------------- /public/pages/workflows/import_workflow/import_workflow_modal.test.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react'; 7 | import { render } from '@testing-library/react'; 8 | import { Provider } from 'react-redux'; 9 | import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; 10 | import { store } from '../../../store'; 11 | import { ImportWorkflowModal } from './import_workflow_modal'; 12 | 13 | jest.mock('../../../services', () => { 14 | const { mockCoreServices } = require('../../../../test'); 15 | return { 16 | ...jest.requireActual('../../../services'), 17 | ...mockCoreServices, 18 | }; 19 | }); 20 | 21 | const renderWithRouter = () => 22 | render( 23 | 24 | 25 | 26 | ( 28 | 33 | )} 34 | /> 35 | 36 | 37 | 38 | ); 39 | 40 | describe('ImportWorkflowModal', () => { 41 | beforeEach(() => { 42 | jest.clearAllMocks(); 43 | }); 44 | test('renders the page', () => { 45 | const { getAllByText } = renderWithRouter(); 46 | expect( 47 | getAllByText('Import a workflow (JSON/YAML)').length 48 | ).toBeGreaterThan(0); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /public/pages/workflows/import_workflow/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export { ImportWorkflowModal } from './import_workflow_modal'; 7 | -------------------------------------------------------------------------------- /public/pages/workflows/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export * from './workflows'; 7 | -------------------------------------------------------------------------------- /public/pages/workflows/new_workflow/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export { NewWorkflow } from './new_workflow'; 7 | export { fetchEmptyMetadata, fetchEmptyUIConfig } from './utils'; 8 | -------------------------------------------------------------------------------- /public/pages/workflows/new_workflow/use_case.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React, { useState } from 'react'; 7 | import { 8 | EuiText, 9 | EuiFlexGroup, 10 | EuiFlexItem, 11 | EuiCard, 12 | EuiSmallButton, 13 | } from '@elastic/eui'; 14 | import { Workflow } from '../../../../common'; 15 | import { QuickConfigureModal } from './quick_configure_modal'; 16 | 17 | interface UseCaseProps { 18 | workflow: Workflow; 19 | } 20 | 21 | export function UseCase(props: UseCaseProps) { 22 | // name modal state 23 | const [isNameModalOpen, setIsNameModalOpen] = useState(false); 24 | 25 | return ( 26 | <> 27 | {isNameModalOpen && ( 28 | setIsNameModalOpen(false)} 31 | /> 32 | )} 33 | 36 |

{props.workflow.name}

37 | 38 | } 39 | titleSize="s" 40 | paddingSize="l" 41 | textAlign="left" 42 | description={props.workflow?.description || ''} 43 | footer={ 44 | 45 | 46 | { 50 | setIsNameModalOpen(true); 51 | }} 52 | data-testid="goButton" 53 | > 54 | Create 55 | 56 | 57 | 58 | } 59 | >
60 | 61 | ); 62 | } 63 | -------------------------------------------------------------------------------- /public/pages/workflows/workflow_list/columns.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react'; 7 | import { EuiLink } from '@elastic/eui'; 8 | import { 9 | EMPTY_FIELD_STRING, 10 | MAX_WORKFLOW_NAME_TO_DISPLAY, 11 | Workflow, 12 | getCharacterLimitedString, 13 | toFormattedDate, 14 | } from '../../../../common'; 15 | import { 16 | constructHrefWithDataSourceId, 17 | getDataSourceId, 18 | } from '../../../utils/utils'; 19 | 20 | export const columns = (actions: any[]) => { 21 | const dataSourceId = getDataSourceId(); 22 | 23 | return [ 24 | { 25 | field: 'name', 26 | name: 'Name', 27 | width: '25%', 28 | sortable: true, 29 | render: (name: string, workflow: Workflow) => ( 30 | 36 | {getCharacterLimitedString(name, MAX_WORKFLOW_NAME_TO_DISPLAY)} 37 | 38 | ), 39 | }, 40 | { 41 | field: 'ui_metadata.type', 42 | name: 'Type', 43 | width: '25%', 44 | sortable: true, 45 | }, 46 | { 47 | field: 'description', 48 | name: 'Description', 49 | width: '35%', 50 | sortable: false, 51 | }, 52 | { 53 | field: 'lastUpdated', 54 | name: 'Last saved', 55 | width: '15%', 56 | sortable: true, 57 | render: (lastUpdated: number) => 58 | lastUpdated !== undefined 59 | ? toFormattedDate(lastUpdated) 60 | : EMPTY_FIELD_STRING, 61 | }, 62 | { 63 | name: 'Actions', 64 | width: '10%', 65 | actions, 66 | }, 67 | ]; 68 | }; 69 | -------------------------------------------------------------------------------- /public/pages/workflows/workflow_list/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export { WorkflowList } from './workflow_list'; 7 | -------------------------------------------------------------------------------- /public/pages/workflows/workflows.test.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react'; 7 | import { render, waitFor } from '@testing-library/react'; 8 | import '@testing-library/jest-dom'; 9 | import userEvent from '@testing-library/user-event'; 10 | import { Provider } from 'react-redux'; 11 | 12 | import { 13 | BrowserRouter as Router, 14 | RouteComponentProps, 15 | Route, 16 | Switch, 17 | } from 'react-router-dom'; 18 | import { store } from '../../store'; 19 | import { Workflows } from './workflows'; 20 | 21 | jest.mock('../../services', () => { 22 | const { mockCoreServices } = require('../../../test'); 23 | return { 24 | ...jest.requireActual('../../services'), 25 | ...mockCoreServices, 26 | }; 27 | }); 28 | 29 | const renderWithRouter = () => ({ 30 | ...render( 31 | 32 | 33 | 34 | ( 36 | 37 | )} 38 | /> 39 | 40 | 41 | 42 | ), 43 | }); 44 | 45 | describe('Workflows', () => { 46 | beforeEach(() => { 47 | jest.clearAllMocks(); 48 | }); 49 | test('renders the page', async () => { 50 | const { getAllByText, getByTestId, queryByText } = renderWithRouter(); 51 | 52 | // The "Manage Workflows" tab is displayed by default 53 | 54 | // Import Workflow Testing 55 | expect(getAllByText('Workflows').length).toBeGreaterThan(0); 56 | const importWorkflowButton = getByTestId('importWorkflowButton'); 57 | userEvent.click(importWorkflowButton); 58 | await waitFor(() => { 59 | expect( 60 | getAllByText('Select or drag and drop a file').length 61 | ).toBeGreaterThan(0); 62 | }); 63 | 64 | // Closing or canceling the import 65 | const cancelImportButton = getByTestId('cancelImportButton'); 66 | userEvent.click(cancelImportButton); 67 | await waitFor(() => { 68 | expect( 69 | queryByText('Select or drag and drop a file') 70 | ).not.toBeInTheDocument(); 71 | }); 72 | 73 | // When the "Create Workflow" button is clicked, the "New workflow" tab opens 74 | // Create Workflow Testing 75 | const createWorkflowButton = getByTestId('createWorkflowButton'); 76 | expect(createWorkflowButton).toBeInTheDocument(); 77 | userEvent.click(createWorkflowButton); 78 | await waitFor(() => { 79 | expect( 80 | getAllByText('Create a workflow using a template').length 81 | ).toBeGreaterThan(0); 82 | }); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /public/plugin.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { 7 | AppMountParameters, 8 | CoreSetup, 9 | DEFAULT_NAV_GROUPS, 10 | DEFAULT_APP_CATEGORIES, 11 | CoreStart, 12 | Plugin, 13 | } from '../../../src/core/public'; 14 | import { 15 | FlowFrameworkDashboardsPluginStart, 16 | FlowFrameworkDashboardsPluginSetup, 17 | AppPluginStartDependencies, 18 | } from './types'; 19 | import { registerPluginCard } from './general_components'; 20 | import { PLUGIN_ID, PLUGIN_NAME } from '../common'; 21 | import { 22 | setCore, 23 | setRouteService, 24 | setSavedObjectsClient, 25 | setDataSourceManagementPlugin, 26 | setDataSourceEnabled, 27 | setNotifications, 28 | setNavigationUI, 29 | setApplication, 30 | setUISettings, 31 | setHeaderActionMenu, 32 | } from './services'; 33 | import { configureRoutes } from './route_service'; 34 | 35 | export class FlowFrameworkDashboardsPlugin 36 | implements 37 | Plugin< 38 | FlowFrameworkDashboardsPluginSetup, 39 | FlowFrameworkDashboardsPluginStart 40 | > { 41 | public setup( 42 | core: CoreSetup, 43 | plugins: any 44 | ): FlowFrameworkDashboardsPluginSetup { 45 | // Register the plugin in the side navigation 46 | core.application.register({ 47 | id: PLUGIN_ID, 48 | title: PLUGIN_NAME, 49 | category: { 50 | id: 'opensearch', 51 | label: 'OpenSearch plugins', 52 | // TODO: this may change after plugin position is finalized 53 | order: 2000, 54 | }, 55 | // TODO: can i remove this below order 56 | order: 5000, 57 | async mount(params: AppMountParameters) { 58 | const { renderApp } = await import('./render_app'); 59 | const [coreStart] = await core.getStartServices(); 60 | const routeServices = configureRoutes(coreStart); 61 | setCore(coreStart); 62 | setHeaderActionMenu(params.setHeaderActionMenu); 63 | setRouteService(routeServices); 64 | return renderApp(coreStart, params); 65 | }, 66 | }); 67 | core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.search, [ 68 | { 69 | id: PLUGIN_ID, 70 | title: PLUGIN_NAME, 71 | category: DEFAULT_APP_CATEGORIES.configure, 72 | showInAllNavGroup: true, 73 | }, 74 | ]); 75 | setUISettings(core.uiSettings); 76 | setDataSourceManagementPlugin(plugins.dataSourceManagement); 77 | const enabled = !!plugins.dataSource; 78 | setDataSourceEnabled({ enabled }); 79 | return { 80 | dataSourceManagement: plugins.dataSourceManagement, 81 | dataSource: plugins.dataSource, 82 | }; 83 | } 84 | 85 | public start( 86 | core: CoreStart, 87 | { navigation, contentManagement }: AppPluginStartDependencies 88 | ): FlowFrameworkDashboardsPluginStart { 89 | setNotifications(core.notifications); 90 | setSavedObjectsClient(core.savedObjects.client); 91 | setNavigationUI(navigation.ui); 92 | setApplication(core.application); 93 | if (contentManagement) { 94 | registerPluginCard(contentManagement, core); 95 | } 96 | return {}; 97 | } 98 | 99 | public stop() {} 100 | } 101 | -------------------------------------------------------------------------------- /public/render_app.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import React from 'react'; 7 | import ReactDOM from 'react-dom'; 8 | import { HashRouter as Router, Route } from 'react-router-dom'; 9 | import { Provider } from 'react-redux'; 10 | import { AppMountParameters, CoreStart } from '../../../src/core/public'; 11 | import { FlowFrameworkDashboardsApp } from './app'; 12 | import { store } from './store'; 13 | 14 | // styling 15 | import './global-styles.scss'; 16 | 17 | export const renderApp = (coreStart: CoreStart, params: AppMountParameters) => { 18 | // This is so our base element stretches to fit the entire webpage 19 | params.element.className = 'stretch-absolute'; 20 | ReactDOM.render( 21 | 22 | 23 | ( 25 | 29 | )} 30 | /> 31 | 32 | , 33 | params.element 34 | ); 35 | 36 | const unlistenParentHistory = params.history.listen(() => { 37 | window.dispatchEvent(new HashChangeEvent('hashchange')); 38 | }); 39 | 40 | return () => { 41 | ReactDOM.unmountComponentAtNode(params.element); 42 | unlistenParentHistory(); 43 | }; 44 | }; 45 | -------------------------------------------------------------------------------- /public/services.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { createGetterSetter } from '../../../src/plugins/opensearch_dashboards_utils/public'; 7 | import { 8 | CoreStart, 9 | NotificationsStart, 10 | IUiSettingsClient, 11 | AppMountParameters, 12 | } from '../../../src/core/public'; 13 | import { RouteService } from './route_service'; 14 | import { DataSourceManagementPluginSetup } from '../../../src/plugins/data_source_management/public'; 15 | import { NavigationPublicPluginStart } from '../../../src/plugins/navigation/public'; 16 | 17 | export interface DataSourceEnabled { 18 | enabled: boolean; 19 | } 20 | 21 | export const [getCore, setCore] = createGetterSetter('Core'); 22 | 23 | export const [getRouteService, setRouteService] = createGetterSetter< 24 | RouteService 25 | >(''); 26 | 27 | export const [ 28 | getSavedObjectsClient, 29 | setSavedObjectsClient, 30 | ] = createGetterSetter( 31 | 'SavedObjectsClient' 32 | ); 33 | 34 | export const [ 35 | getDataSourceManagementPlugin, 36 | setDataSourceManagementPlugin, 37 | ] = createGetterSetter('DataSourceManagement'); 38 | 39 | export const [getDataSourceEnabled, setDataSourceEnabled] = createGetterSetter< 40 | DataSourceEnabled 41 | >('DataSourceEnabled'); 42 | 43 | export const [getNotifications, setNotifications] = createGetterSetter< 44 | NotificationsStart 45 | >('Notifications'); 46 | 47 | export const [getUISettings, setUISettings] = createGetterSetter< 48 | IUiSettingsClient 49 | >('UISettings'); 50 | 51 | export const [getApplication, setApplication] = createGetterSetter< 52 | CoreStart['application'] 53 | >('Application'); 54 | 55 | export const [getNavigationUI, setNavigationUI] = createGetterSetter< 56 | NavigationPublicPluginStart['ui'] 57 | >('Navigation'); 58 | 59 | export const [getHeaderActionMenu, setHeaderActionMenu] = createGetterSetter< 60 | AppMountParameters['setHeaderActionMenu'] 61 | >('SetHeaderActionMenu'); 62 | -------------------------------------------------------------------------------- /public/store/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export * from './store'; 7 | export * from './reducers'; 8 | -------------------------------------------------------------------------------- /public/store/reducers/errors_reducer.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; 7 | import { IngestPipelineErrors, SearchPipelineErrors } from '../../../common'; 8 | 9 | export const INITIAL_ERRORS_STATE = { 10 | ingestPipeline: {} as IngestPipelineErrors, 11 | searchPipeline: {} as SearchPipelineErrors, 12 | }; 13 | 14 | const ERRORS_PREFIX = 'errors'; 15 | const SET_INGEST_PIPELINE_ERRORS = `${ERRORS_PREFIX}/setIngestPipelineErrors`; 16 | const SET_SEARCH_PIPELINE_ERRORS = `${ERRORS_PREFIX}/setSearchPipelineErrors`; 17 | 18 | export const setIngestPipelineErrors = createAsyncThunk( 19 | SET_INGEST_PIPELINE_ERRORS, 20 | async ({ errors }: { errors: IngestPipelineErrors }, { rejectWithValue }) => { 21 | return errors; 22 | } 23 | ); 24 | 25 | export const setSearchPipelineErrors = createAsyncThunk( 26 | SET_SEARCH_PIPELINE_ERRORS, 27 | async ({ errors }: { errors: SearchPipelineErrors }, { rejectWithValue }) => { 28 | return errors; 29 | } 30 | ); 31 | 32 | const errorsSlice = createSlice({ 33 | name: ERRORS_PREFIX, 34 | initialState: INITIAL_ERRORS_STATE, 35 | reducers: {}, 36 | extraReducers: (builder) => { 37 | builder 38 | .addCase(setIngestPipelineErrors.fulfilled, (state, action) => { 39 | state.ingestPipeline = action.payload; 40 | }) 41 | .addCase(setSearchPipelineErrors.fulfilled, (state, action) => { 42 | state.searchPipeline = action.payload; 43 | }); 44 | }, 45 | }); 46 | 47 | export const errorsReducer = errorsSlice.reducer; 48 | -------------------------------------------------------------------------------- /public/store/reducers/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export * from './opensearch_reducer'; 7 | export * from './workflows_reducer'; 8 | export * from './presets_reducer'; 9 | export * from './ml_reducer'; 10 | export * from './errors_reducer'; 11 | -------------------------------------------------------------------------------- /public/store/reducers/ml_reducer.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; 7 | import { ConnectorDict, ModelDict } from '../../../common'; 8 | import { HttpFetchError } from '../../../../../src/core/public'; 9 | import { getRouteService } from '../../services'; 10 | 11 | export const INITIAL_ML_STATE = { 12 | loading: false, 13 | errorMessage: '', 14 | models: {} as ModelDict, 15 | connectors: {} as ConnectorDict, 16 | }; 17 | 18 | const MODELS_ACTION_PREFIX = 'models'; 19 | const CONNECTORS_ACTION_PREFIX = 'connectors'; 20 | const SEARCH_MODELS_ACTION = `${MODELS_ACTION_PREFIX}/search`; 21 | const SEARCH_CONNECTORS_ACTION = `${CONNECTORS_ACTION_PREFIX}/search`; 22 | 23 | export const searchModels = createAsyncThunk( 24 | SEARCH_MODELS_ACTION, 25 | async ( 26 | { apiBody, dataSourceId }: { apiBody: {}; dataSourceId?: string }, 27 | { rejectWithValue } 28 | ) => { 29 | const response: any | HttpFetchError = await getRouteService().searchModels( 30 | apiBody, 31 | dataSourceId 32 | ); 33 | if (response instanceof HttpFetchError) { 34 | return rejectWithValue( 35 | 'Error searching models: ' + response.body.message 36 | ); 37 | } else { 38 | return response; 39 | } 40 | } 41 | ); 42 | 43 | export const searchConnectors = createAsyncThunk( 44 | SEARCH_CONNECTORS_ACTION, 45 | async ( 46 | { apiBody, dataSourceId }: { apiBody: {}; dataSourceId?: string }, 47 | { rejectWithValue } 48 | ) => { 49 | const response: 50 | | any 51 | | HttpFetchError = await getRouteService().searchConnectors( 52 | apiBody, 53 | dataSourceId 54 | ); 55 | if (response instanceof HttpFetchError) { 56 | return rejectWithValue( 57 | 'Error searching connectors: ' + response.body.message 58 | ); 59 | } else { 60 | return response; 61 | } 62 | } 63 | ); 64 | 65 | const mlSlice = createSlice({ 66 | name: 'ml', 67 | initialState: INITIAL_ML_STATE, 68 | reducers: {}, 69 | extraReducers: (builder) => { 70 | builder 71 | // Pending states 72 | .addCase(searchModels.pending, (state, action) => { 73 | state.loading = true; 74 | state.errorMessage = ''; 75 | }) 76 | .addCase(searchConnectors.pending, (state, action) => { 77 | state.loading = true; 78 | state.errorMessage = ''; 79 | }) 80 | // Fulfilled states 81 | .addCase(searchModels.fulfilled, (state, action) => { 82 | const { models } = action.payload as { models: ModelDict }; 83 | state.models = models; 84 | state.loading = false; 85 | state.errorMessage = ''; 86 | }) 87 | .addCase(searchConnectors.fulfilled, (state, action) => { 88 | const { connectors } = action.payload as { connectors: ConnectorDict }; 89 | state.connectors = connectors; 90 | state.loading = false; 91 | state.errorMessage = ''; 92 | }) 93 | // Rejected states 94 | .addCase(searchModels.rejected, (state, action) => { 95 | state.errorMessage = action.payload as string; 96 | state.loading = false; 97 | }) 98 | .addCase(searchConnectors.rejected, (state, action) => { 99 | state.errorMessage = action.payload as string; 100 | state.loading = false; 101 | }); 102 | }, 103 | }); 104 | 105 | export const mlReducer = mlSlice.reducer; 106 | -------------------------------------------------------------------------------- /public/store/reducers/presets_reducer.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; 7 | import { WorkflowTemplate } from '../../../common'; 8 | import { HttpFetchError } from '../../../../../src/core/public'; 9 | import { getRouteService } from '../../services'; 10 | 11 | export const INITIAL_PRESETS_STATE = { 12 | loading: false, 13 | errorMessage: '', 14 | presetWorkflows: [] as Partial[], 15 | }; 16 | 17 | const PRESET_ACTION_PREFIX = 'presets'; 18 | const GET_WORKFLOW_PRESETS_ACTION = `${PRESET_ACTION_PREFIX}/getPresets`; 19 | 20 | export const getWorkflowPresets = createAsyncThunk( 21 | GET_WORKFLOW_PRESETS_ACTION, 22 | async (_, { rejectWithValue }) => { 23 | const response: 24 | | any 25 | | HttpFetchError = await getRouteService().getWorkflowPresets(); 26 | if (response instanceof HttpFetchError) { 27 | return rejectWithValue( 28 | 'Error getting workflow presets: ' + response.body.message 29 | ); 30 | } else { 31 | return response; 32 | } 33 | } 34 | ); 35 | 36 | const presetsSlice = createSlice({ 37 | name: 'presets', 38 | initialState: INITIAL_PRESETS_STATE, 39 | reducers: {}, 40 | extraReducers: (builder) => { 41 | builder 42 | .addCase(getWorkflowPresets.pending, (state, action) => { 43 | state.loading = true; 44 | state.errorMessage = ''; 45 | }) 46 | .addCase(getWorkflowPresets.fulfilled, (state, action) => { 47 | state.presetWorkflows = action.payload.workflowTemplates as Partial< 48 | WorkflowTemplate 49 | >[]; 50 | state.loading = false; 51 | state.errorMessage = ''; 52 | }) 53 | .addCase(getWorkflowPresets.rejected, (state, action) => { 54 | state.loading = false; 55 | state.errorMessage = action.payload as string; 56 | }); 57 | }, 58 | }); 59 | 60 | export const presetsReducer = presetsSlice.reducer; 61 | -------------------------------------------------------------------------------- /public/store/store.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { ThunkDispatch, configureStore } from '@reduxjs/toolkit'; 7 | import { AnyAction, combineReducers } from 'redux'; 8 | import { useDispatch } from 'react-redux'; 9 | import { 10 | opensearchReducer, 11 | workflowsReducer, 12 | presetsReducer, 13 | mlReducer, 14 | errorsReducer, 15 | } from './reducers'; 16 | 17 | const rootReducer = combineReducers({ 18 | workflows: workflowsReducer, 19 | presets: presetsReducer, 20 | ml: mlReducer, 21 | opensearch: opensearchReducer, 22 | errors: errorsReducer, 23 | }); 24 | 25 | export const store = configureStore({ 26 | reducer: rootReducer, 27 | }); 28 | 29 | export type AppState = ReturnType; 30 | export type AppThunkDispatch = ThunkDispatch; 31 | export const useAppDispatch = () => useDispatch(); 32 | -------------------------------------------------------------------------------- /public/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { NavigationPublicPluginStart } from '../../../src/plugins/navigation/public'; 7 | import { DataSourceManagementPluginSetup } from '../../../src/plugins/data_source_management/public'; 8 | import { DataSourcePluginSetup } from '../../../src/plugins/data_source/public'; 9 | import { ContentManagementPluginStart } from '../../../src/plugins/content_management/public'; 10 | 11 | export interface FlowFrameworkDashboardsPluginSetup { 12 | dataSourceManagement: DataSourceManagementPluginSetup; 13 | dataSource: DataSourcePluginSetup; 14 | } 15 | 16 | export interface FlowFrameworkDashboardsPluginStart { 17 | } 18 | 19 | export interface AppPluginStartDependencies { 20 | navigation: NavigationPublicPluginStart; 21 | contentManagement?: ContentManagementPluginStart; 22 | } 23 | -------------------------------------------------------------------------------- /public/utils/constants.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { constructHrefWithDataSourceId } from './utils'; 7 | import { getUISettings } from '../../public/services'; 8 | import { PLUGIN_NAME } from '../../common/constants'; 9 | 10 | export enum Navigation { 11 | PluginName = PLUGIN_NAME, 12 | Workflows = 'Workflows', 13 | } 14 | 15 | export enum APP_PATH { 16 | HOME = '/', 17 | WORKFLOWS = '/workflows', 18 | WORKFLOW_DETAIL = '/workflows/:workflowId', 19 | } 20 | 21 | export const BREADCRUMBS = Object.freeze({ 22 | PLUGIN_NAME: { text: PLUGIN_NAME }, 23 | WORKFLOWS: (dataSourceId?: string) => ({ 24 | text: 'Workflows', 25 | href: constructHrefWithDataSourceId(APP_PATH.WORKFLOWS, dataSourceId), 26 | }), 27 | TITLE: { text: PLUGIN_NAME }, 28 | TITLE_WITH_REF: (dataSourceId?: string) => ({ 29 | text: PLUGIN_NAME, 30 | href: constructHrefWithDataSourceId(APP_PATH.WORKFLOWS, dataSourceId), 31 | }), 32 | WORKFLOW_NAME: (workflowName: string) => ({ 33 | text: workflowName, 34 | }), 35 | }); 36 | 37 | export const USE_NEW_HOME_PAGE = getUISettings().get('home:useNewHomePage'); 38 | 39 | export const getAppBasePath = () => { 40 | const currentPath = window.location.pathname; 41 | return currentPath.substring(0, currentPath.indexOf('/app/')); 42 | }; -------------------------------------------------------------------------------- /public/utils/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export * from './constants'; 7 | export * from './utils'; 8 | export * from './config_to_template_utils'; 9 | export * from './config_to_form_utils'; 10 | export * from './config_to_workspace_utils'; 11 | export * from './config_to_schema_utils'; 12 | export * from './form_to_config_utils'; 13 | export * from './form_to_pipeline_utils'; 14 | -------------------------------------------------------------------------------- /release-notes/opensearch-flow-framework-dashboards.release-notes-2.19.0.0.md: -------------------------------------------------------------------------------- 1 | ## Version 2.19.0.0 Release Notes 2 | 3 | The initial release of the Flow Framework OpenSearch Dashboards Plugin. Compatible with OpenSearch Dashboards 2.19.0 4 | 5 | This plugin provides a UI for building and testing AI/ML use cases. For further information, examples, and documentation, see [here](https://github.com/opensearch-project/dashboards-flow-framework/blob/2.x/documentation/tutorial.md). -------------------------------------------------------------------------------- /release-notes/opensearch-flow-framework-dashboards.release-notes-3.0.0.0-alpha1.md: -------------------------------------------------------------------------------- 1 | ## Version 3.0.0.0-alpha1 Release Notes 2 | 3 | Compatible with OpenSearch 3.0.0.0-alpha1 4 | ### Features 5 | - Add fine-grained error handling ([#598](https://github.com/opensearch-project/dashboards-flow-framework/pull/598)) 6 | - Change ingestion input to JSON lines format ([#639](https://github.com/opensearch-project/dashboards-flow-framework/pull/639)) 7 | 8 | ### Enhancements 9 | - Integrate legacy presets with quick-configure fields ([#602](https://github.com/opensearch-project/dashboards-flow-framework/pull/602)) 10 | - Simplify RAG presets, add bulk API details ([#610](https://github.com/opensearch-project/dashboards-flow-framework/pull/610)) 11 | - Improve RAG preset experience ([#617](https://github.com/opensearch-project/dashboards-flow-framework/pull/617)) 12 | - Update model options and callout ([#622](https://github.com/opensearch-project/dashboards-flow-framework/pull/622)) 13 | - Added popover to display links to suggested models ([#625](https://github.com/opensearch-project/dashboards-flow-framework/pull/625)) 14 | - Implicitly update input maps defined on non-expanded queries (common cases) ([#632](https://github.com/opensearch-project/dashboards-flow-framework/pull/632)) 15 | - Show interim JSON provision flow even if provisioned ([#633](https://github.com/opensearch-project/dashboards-flow-framework/pull/633)) 16 | - Add functional buttons in form headers, fix query parse bug ([#649](https://github.com/opensearch-project/dashboards-flow-framework/pull/649)) 17 | - Block simulate API calls if datasource version is missing ([#657](https://github.com/opensearch-project/dashboards-flow-framework/pull/657)) 18 | - Update default queries, update quick config fields, misc updates ([#660](https://github.com/opensearch-project/dashboards-flow-framework/pull/660)) 19 | - Update visible plugin name to 'AI Search Flows' ([#662](https://github.com/opensearch-project/dashboards-flow-framework/pull/662)) 20 | - Update plugin name and rearrange Try AI Search Flows card ([#664](https://github.com/opensearch-project/dashboards-flow-framework/pull/664)) 21 | 22 | ### Bug Fixes 23 | - Fix error that local cluster cannot get version ([#606](https://github.com/opensearch-project/dashboards-flow-framework/pull/606)) 24 | - UX fit-n-finish updates XI ([#613](https://github.com/opensearch-project/dashboards-flow-framework/pull/613)) 25 | - UX fit-n-finish updates XII ([#618](https://github.com/opensearch-project/dashboards-flow-framework/pull/618)) 26 | - Bug fixes XIII ([#630](https://github.com/opensearch-project/dashboards-flow-framework/pull/630)) 27 | - Various bug fixes & improvements ([#644](https://github.com/opensearch-project/dashboards-flow-framework/pull/644)) 28 | - Fixed bug related to Search Index in Local Cluster scenario ([#654](https://github.com/opensearch-project/dashboards-flow-framework/pull/654)) 29 | 30 | ### Maintenance 31 | - Support 2.17 BWC with latest backend integrations ([#612](https://github.com/opensearch-project/dashboards-flow-framework/pull/612)) 32 | 33 | ### Refactoring 34 | - Refactor quick configure components, improve processor error handling ([#604](https://github.com/opensearch-project/dashboards-flow-framework/pull/604)) 35 | - Hide search query section when version is less than 2.19 ([#605](https://github.com/opensearch-project/dashboards-flow-framework/pull/605)) 36 | -------------------------------------------------------------------------------- /release-notes/opensearch-flow-framework-dashboards.release-notes-3.0.0.0-beta1.md: -------------------------------------------------------------------------------- 1 | ## Version 3.0.0.0-beta1 Release Notes 2 | 3 | Compatible with OpenSearch 3.0.0.0-beta1 4 | 5 | ### Enhancements 6 | - Add new RAG + hybrid search preset ([#665](https://github.com/opensearch-project/dashboards-flow-framework/pull/665)) 7 | - Update new index mappings if selecting from existing index ([#670](https://github.com/opensearch-project/dashboards-flow-framework/pull/670)) 8 | - Persist state across Inspector tab switches; add presets dropdown ([#671](https://github.com/opensearch-project/dashboards-flow-framework/pull/671)) 9 | - Simplify ML processor form when interface is defined ([#676](https://github.com/opensearch-project/dashboards-flow-framework/pull/676)) 10 | - Cache form across ML transform types ([#678](https://github.com/opensearch-project/dashboards-flow-framework/pull/678)) 11 | 12 | ### Bug Fixes 13 | - Fix missed UI autofilling after JSON Lines change ([#672](https://github.com/opensearch-project/dashboards-flow-framework/pull/672)) -------------------------------------------------------------------------------- /server/cluster/core_plugin.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { SEARCH_PIPELINE_ROUTE } from '../../common'; 7 | 8 | export function corePlugin(Client: any, config: any, components: any) { 9 | const ca = components.clientAction.factory; 10 | 11 | Client.prototype.coreClient = components.clientAction.namespaceFactory(); 12 | const coreClient = Client.prototype.coreClient.prototype; 13 | 14 | coreClient.getSearchPipeline = ca({ 15 | url: { 16 | fmt: `${SEARCH_PIPELINE_ROUTE}/<%=pipeline_id%>`, 17 | req: { 18 | pipeline_id: { 19 | type: 'string', 20 | required: true, 21 | }, 22 | }, 23 | }, 24 | method: 'GET', 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /server/cluster/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export * from './flow_framework_plugin'; 7 | export * from './ml_plugin'; 8 | export * from './core_plugin'; 9 | -------------------------------------------------------------------------------- /server/cluster/ml_plugin.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { 7 | ML_SEARCH_CONNECTORS_ROUTE, 8 | ML_SEARCH_MODELS_ROUTE, 9 | } from '../../common'; 10 | 11 | /** 12 | * Used during the plugin's setup() lifecycle phase to register various client actions 13 | * representing ML plugin APIs. These are then exposed and used on the 14 | * server-side when processing node APIs - see server/routes/ml_routes_service 15 | * for examples. 16 | */ 17 | export function mlPlugin(Client: any, config: any, components: any) { 18 | const ca = components.clientAction.factory; 19 | 20 | Client.prototype.mlClient = components.clientAction.namespaceFactory(); 21 | const mlClient = Client.prototype.mlClient.prototype; 22 | 23 | mlClient.searchModels = ca({ 24 | url: { 25 | fmt: ML_SEARCH_MODELS_ROUTE, 26 | }, 27 | needBody: true, 28 | method: 'POST', 29 | }); 30 | 31 | mlClient.searchConnectors = ca({ 32 | url: { 33 | fmt: ML_SEARCH_CONNECTORS_ROUTE, 34 | }, 35 | needBody: true, 36 | method: 'POST', 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /server/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { 7 | PluginConfigDescriptor, 8 | PluginInitializerContext, 9 | } from '../../../src/core/server'; 10 | import { FlowFrameworkDashboardsPlugin } from './plugin'; 11 | import { configSchema, ConfigSchema } from '../server/types'; 12 | 13 | export const config: PluginConfigDescriptor = { 14 | schema: configSchema, 15 | }; 16 | 17 | // This exports static code and TypeScript types, 18 | // as well as, OpenSearch Dashboards Platform `plugin()` initializer. 19 | 20 | export function plugin(initializerContext: PluginInitializerContext) { 21 | return new FlowFrameworkDashboardsPlugin(initializerContext); 22 | } 23 | 24 | export { 25 | FlowFrameworkDashboardsPluginSetup, 26 | FlowFrameworkDashboardsPluginStart, 27 | } from './types'; 28 | -------------------------------------------------------------------------------- /server/plugin.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { 7 | PluginInitializerContext, 8 | CoreSetup, 9 | CoreStart, 10 | Plugin, 11 | Logger, 12 | } from '../../../src/core/server'; 13 | import { first } from 'rxjs/operators'; 14 | import { flowFrameworkPlugin, mlPlugin, corePlugin } from './cluster'; 15 | import { 16 | FlowFrameworkDashboardsPluginSetup, 17 | FlowFrameworkDashboardsPluginStart, 18 | } from './types'; 19 | import { 20 | registerOpenSearchRoutes, 21 | registerFlowFrameworkRoutes, 22 | OpenSearchRoutesService, 23 | FlowFrameworkRoutesService, 24 | registerMLRoutes, 25 | MLRoutesService, 26 | } from './routes'; 27 | import { DataSourcePluginSetup } from '../../../src/plugins/data_source/server/types'; 28 | import { DataSourceManagementPlugin } from '../../../src/plugins/data_source_management/public'; 29 | 30 | import { ILegacyClusterClient } from '../../../src/core/server/'; 31 | 32 | export interface FlowFrameworkPluginSetupDependencies { 33 | dataSourceManagement?: ReturnType; 34 | dataSource?: DataSourcePluginSetup; 35 | } 36 | 37 | export class FlowFrameworkDashboardsPlugin 38 | implements 39 | Plugin< 40 | FlowFrameworkDashboardsPluginSetup, 41 | FlowFrameworkDashboardsPluginStart 42 | > { 43 | private readonly logger: Logger; 44 | private readonly globalConfig$: any; 45 | 46 | constructor(initializerContext: PluginInitializerContext) { 47 | this.logger = initializerContext.logger.get(); 48 | this.globalConfig$ = initializerContext.config.legacy.globalConfig$; 49 | } 50 | 51 | public async setup( 52 | core: CoreSetup, 53 | { dataSource }: FlowFrameworkPluginSetupDependencies 54 | ) { 55 | this.logger.debug('flow-framework-dashboards: Setup'); 56 | const router = core.http.createRouter(); 57 | 58 | // Get global config 59 | const globalConfig = await this.globalConfig$.pipe(first()).toPromise(); 60 | 61 | // Create OpenSearch client, including flow framework plugin APIs 62 | const client: ILegacyClusterClient = core.opensearch.legacy.createClient( 63 | 'flow_framework', 64 | { 65 | plugins: [flowFrameworkPlugin, mlPlugin, corePlugin], 66 | ...globalConfig.opensearch, 67 | } 68 | ); 69 | 70 | const dataSourceEnabled = !!dataSource; 71 | if (dataSourceEnabled) { 72 | dataSource.registerCustomApiSchema(flowFrameworkPlugin); 73 | dataSource.registerCustomApiSchema(mlPlugin); 74 | dataSource.registerCustomApiSchema(corePlugin); 75 | } 76 | const opensearchRoutesService = new OpenSearchRoutesService( 77 | client, 78 | dataSourceEnabled 79 | ); 80 | const flowFrameworkRoutesService = new FlowFrameworkRoutesService( 81 | client, 82 | dataSourceEnabled 83 | ); 84 | const mlRoutesService = new MLRoutesService(client, dataSourceEnabled); 85 | 86 | // Register server side APIs with the corresponding service functions 87 | registerOpenSearchRoutes(router, opensearchRoutesService); 88 | registerFlowFrameworkRoutes(router, flowFrameworkRoutesService); 89 | registerMLRoutes(router, mlRoutesService); 90 | 91 | return {}; 92 | } 93 | 94 | public start(core: CoreStart) { 95 | this.logger.debug('flow-framework-dashboards: Started'); 96 | return {}; 97 | } 98 | 99 | public stop() {} 100 | } 101 | -------------------------------------------------------------------------------- /server/resources/templates/custom.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Custom Search", 3 | "description": "Build a custom workflow tailored to your specific use case without using a template.", 4 | "use_case": "CUSTOM", 5 | "version": { 6 | "template": "1.0.0", 7 | "compatibility": [ 8 | "2.19.0", 9 | "3.0.0" 10 | ] 11 | } 12 | } -------------------------------------------------------------------------------- /server/resources/templates/hybrid_search.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Hybrid Search", 3 | "description": "Build an application that searches using a combination of vector and lexical search.", 4 | "version": { 5 | "template": "1.0.0", 6 | "compatibility": [ 7 | "2.19.0", 8 | "3.0.0" 9 | ] 10 | }, 11 | "ui_metadata": { 12 | "type": "Hybrid Search" 13 | } 14 | } -------------------------------------------------------------------------------- /server/resources/templates/hybrid_search_with_rag.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RAG with Hybrid Search", 3 | "description": "Build a search application that uses retrieval-augmented generation (RAG) to retrieve relevant documents using hybrid search, pass them to large language models, and synthesize answers.", 4 | "version": { 5 | "template": "1.0.0", 6 | "compatibility": [ 7 | "2.19.0", 8 | "3.0.0" 9 | ] 10 | }, 11 | "ui_metadata": { 12 | "type": "RAG with Hybrid Search" 13 | } 14 | } -------------------------------------------------------------------------------- /server/resources/templates/multimodal_search.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Multimodal Search", 3 | "description": "Build an application that searches both text and image data using multimodal embedding models.", 4 | "version": { 5 | "template": "1.0.0", 6 | "compatibility": [ 7 | "2.19.0", 8 | "3.0.0" 9 | ] 10 | }, 11 | "ui_metadata": { 12 | "type": "Multimodal Search" 13 | } 14 | } -------------------------------------------------------------------------------- /server/resources/templates/semantic_search.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Semantic Search", 3 | "description": "Build an application that interprets the meaning and context of user queries to deliver more relevant and accurate search results.", 4 | "version": { 5 | "template": "1.0.0", 6 | "compatibility": [ 7 | "2.19.0", 8 | "3.0.0" 9 | ] 10 | }, 11 | "ui_metadata": { 12 | "type": "Semantic Search" 13 | } 14 | } -------------------------------------------------------------------------------- /server/resources/templates/vector_search_with_rag.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RAG with Vector Retrieval", 3 | "description": "Build a search application that uses retrieval-augmented generation (RAG) to retrieve semantically similar documents using vector search, pass them to large language models, and synthesize answers.", 4 | "version": { 5 | "template": "1.0.0", 6 | "compatibility": [ 7 | "2.19.0", 8 | "3.0.0" 9 | ] 10 | }, 11 | "ui_metadata": { 12 | "type": "RAG with Vector Retrieval" 13 | } 14 | } -------------------------------------------------------------------------------- /server/routes/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export * from './opensearch_routes_service'; 7 | export * from './flow_framework_routes_service'; 8 | export * from './ml_routes_service'; 9 | -------------------------------------------------------------------------------- /server/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { schema, TypeOf } from '@osd/config-schema'; 7 | 8 | export const configSchema = schema.object({ 9 | enabled: schema.maybe(schema.boolean()), 10 | }); 11 | 12 | export type ConfigSchema = TypeOf; 13 | 14 | export interface FlowFrameworkDashboardsPluginSetup {} 15 | export interface FlowFrameworkDashboardsPluginStart {} 16 | -------------------------------------------------------------------------------- /server/utils/helpers.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { 7 | ILegacyClusterClient, 8 | OpenSearchDashboardsRequest, 9 | RequestHandlerContext, 10 | } from '../../../../src/core/server'; 11 | 12 | export function getClientBasedOnDataSource( 13 | context: RequestHandlerContext, 14 | dataSourceEnabled: boolean, 15 | request: OpenSearchDashboardsRequest, 16 | dataSourceId: string, 17 | client: ILegacyClusterClient 18 | ): (endpoint: string, clientParams?: Record) => any { 19 | if (dataSourceEnabled && dataSourceId && dataSourceId.trim().length != 0) { 20 | // client for remote cluster 21 | return context.dataSource.opensearch.legacy.getClient(dataSourceId).callAPI; 22 | } else { 23 | // fall back to default local cluster 24 | return client.asScoped(request).callAsCurrentUser; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export * from './mocks'; 7 | -------------------------------------------------------------------------------- /test/interfaces.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { WORKFLOW_TYPE } from '../common/constants'; 7 | 8 | export type WorkflowInput = { 9 | id: string; 10 | name: string; 11 | type: WORKFLOW_TYPE; 12 | version?: string; 13 | }; 14 | -------------------------------------------------------------------------------- /test/jest.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | module.exports = { 7 | rootDir: '../', 8 | roots: [''], 9 | coverageDirectory: './coverage', 10 | // we mock any non-js-related files and return an empty module. This is needed due to errors 11 | // when jest tries to interpret these types of files. 12 | moduleNameMapper: { 13 | '\\.(css|less|scss|sass|svg)$': '/test/mocks/empty_mock.ts', 14 | '@elastic/eui/lib/services/accessibility/html_id_generator': 15 | '/test/mocks/html_id_generator.ts', 16 | 17 | '^uuid$': '/test/mocks/uuid_mock.ts', 18 | '^uuid/.*$': '/test/mocks/uuid_mock.ts', 19 | }, 20 | testEnvironment: 'jest-environment-jsdom', 21 | coverageReporters: ['lcov', 'text', 'cobertura'], 22 | testMatch: ['**/*.test.js', '**/*.test.jsx', '**/*.test.ts', '**/*.test.tsx'], 23 | collectCoverageFrom: [ 24 | '**/*.ts', 25 | '**/*.tsx', 26 | '**/*.js', 27 | '**/*.jsx', 28 | '!**/models/**', 29 | '!**/node_modules/**', 30 | '!**/index.js', 31 | '!/public/app.js', 32 | '!/index.js', 33 | '!/babel.config.js', 34 | '!/test/**', 35 | '!/server/**', 36 | '!/coverage/**', 37 | '!/scripts/**', 38 | '!/build/**', 39 | '!**/vendor/**', 40 | ], 41 | clearMocks: true, 42 | modulePathIgnorePatterns: ['/offline-module-cache/'], 43 | testPathIgnorePatterns: ['/build/', '/node_modules/'], 44 | transformIgnorePatterns: ['/node_modules'], 45 | }; 46 | -------------------------------------------------------------------------------- /test/mocks/empty_mock.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export default {}; 7 | -------------------------------------------------------------------------------- /test/mocks/html_id_generator.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Mock implementation of EUI's htmlIdGenerator 3 | * This avoids the dependency on UUID and crypto.getRandomValues() 4 | */ 5 | 6 | export const htmlIdGenerator = (prefix: string = ''): (() => string) => { 7 | let counter = 0; 8 | return (): string => { 9 | counter += 1; 10 | return `${prefix}${counter}`; 11 | }; 12 | }; 13 | 14 | export default htmlIdGenerator; 15 | -------------------------------------------------------------------------------- /test/mocks/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export { mockCoreServices } from './mock_core_services'; 7 | -------------------------------------------------------------------------------- /test/mocks/mock_core_services.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | export const mockCoreServices = { 7 | getCore: () => { 8 | return { 9 | chrome: { 10 | setBreadcrumbs: jest.fn(), 11 | }, 12 | }; 13 | }, 14 | getNotifications: () => { 15 | return { 16 | toasts: { 17 | addDanger: jest.fn().mockName('addDanger'), 18 | addSuccess: jest.fn().mockName('addSuccess'), 19 | }, 20 | }; 21 | }, 22 | getDataSourceEnabled: () => { 23 | return { 24 | enabled: false, 25 | }; 26 | }, 27 | getUISettings: () => ({ 28 | get: jest.fn((key) => { 29 | if (key === 'home:useNewHomePage') { 30 | return false; 31 | } 32 | }), 33 | }), 34 | getNavigationUI: () => ({ 35 | TopNavMenu: jest.fn(), 36 | HeaderControl: jest.fn(), 37 | }), 38 | 39 | getApplication: () => ({ 40 | setAppRightControls: jest.fn(), 41 | }), 42 | 43 | getHeaderActionMenu: () => jest.fn(), 44 | }; 45 | -------------------------------------------------------------------------------- /test/mocks/uuid_mock.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Mock implementation for UUID package 3 | * This avoids the need for crypto.getRandomValues() in tests 4 | */ 5 | 6 | export function rng(): Uint8Array { 7 | const rnds8 = new Uint8Array(16); 8 | for (let i = 0; i < 16; i++) { 9 | rnds8[i] = Math.floor(Math.random() * 256); 10 | } 11 | return rnds8; 12 | } 13 | 14 | export function v1(): string { 15 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 16 | const r = (Math.random() * 16) | 0; 17 | const v = c === 'x' ? r : (r & 0x3) | 0x8; 18 | return v.toString(16); 19 | }); 20 | } 21 | 22 | export function v4(): string { 23 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 24 | const r = (Math.random() * 16) | 0; 25 | const v = c === 'x' ? r : (r & 0x3) | 0x8; 26 | return v.toString(16); 27 | }); 28 | } 29 | 30 | export default { 31 | v1, 32 | v4, 33 | rng, 34 | }; 35 | -------------------------------------------------------------------------------- /test/utils.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright OpenSearch Contributors 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | import { 7 | INITIAL_ERRORS_STATE, 8 | INITIAL_ML_STATE, 9 | INITIAL_OPENSEARCH_STATE, 10 | INITIAL_PRESETS_STATE, 11 | INITIAL_WORKFLOWS_STATE, 12 | } from '../public/store'; 13 | import { WorkflowInput } from '../test/interfaces'; 14 | import { 15 | MINIMUM_FULL_SUPPORTED_VERSION, 16 | WORKFLOW_TYPE, 17 | } from '../common/constants'; 18 | import { UIState, Workflow, WorkflowDict } from '../common/interfaces'; 19 | import { 20 | fetchEmptyMetadata, 21 | fetchHybridSearchMetadata, 22 | fetchMultimodalSearchMetadata, 23 | fetchSemanticSearchMetadata, 24 | } from '../public/pages/workflows/new_workflow/utils'; 25 | import fs from 'fs'; 26 | import path from 'path'; 27 | 28 | export function mockStore(...workflowSets: WorkflowInput[]) { 29 | let workflowDict = {} as WorkflowDict; 30 | workflowSets?.forEach((workflowInput) => { 31 | workflowDict[workflowInput.id] = generateWorkflow(workflowInput); 32 | }); 33 | return { 34 | getState: () => ({ 35 | opensearch: INITIAL_OPENSEARCH_STATE, 36 | ml: INITIAL_ML_STATE, 37 | workflows: { 38 | ...INITIAL_WORKFLOWS_STATE, 39 | workflows: workflowDict, 40 | }, 41 | presets: INITIAL_PRESETS_STATE, 42 | errors: INITIAL_ERRORS_STATE, 43 | }), 44 | dispatch: jest.fn(), 45 | subscribe: jest.fn(), 46 | replaceReducer: jest.fn(), 47 | [Symbol.observable]: jest.fn(), 48 | }; 49 | } 50 | 51 | function generateWorkflow({ id, name, type }: WorkflowInput): Workflow { 52 | const isSearchWorkflow = [ 53 | WORKFLOW_TYPE.SEMANTIC_SEARCH, 54 | WORKFLOW_TYPE.MULTIMODAL_SEARCH, 55 | WORKFLOW_TYPE.HYBRID_SEARCH, 56 | ].includes(type); 57 | 58 | const version = { 59 | template: '1.0.0', 60 | compatibility: isSearchWorkflow 61 | ? [MINIMUM_FULL_SUPPORTED_VERSION] 62 | : ['2.19.0', '3.0.0'], 63 | }; 64 | 65 | return { 66 | id, 67 | name, 68 | version, 69 | ui_metadata: getConfig(type, version.compatibility[0]), 70 | }; 71 | } 72 | 73 | function getConfig(workflowType: WORKFLOW_TYPE, version?: string) { 74 | let uiMetadata = {} as UIState; 75 | const searchVersion = version || MINIMUM_FULL_SUPPORTED_VERSION; 76 | 77 | switch (workflowType) { 78 | case WORKFLOW_TYPE.SEMANTIC_SEARCH: { 79 | uiMetadata = fetchSemanticSearchMetadata(searchVersion); 80 | break; 81 | } 82 | case WORKFLOW_TYPE.MULTIMODAL_SEARCH: { 83 | uiMetadata = fetchMultimodalSearchMetadata(searchVersion); 84 | break; 85 | } 86 | case WORKFLOW_TYPE.HYBRID_SEARCH: { 87 | uiMetadata = fetchHybridSearchMetadata(searchVersion); 88 | break; 89 | } 90 | default: { 91 | uiMetadata = fetchEmptyMetadata(); 92 | break; 93 | } 94 | } 95 | return uiMetadata; 96 | } 97 | 98 | const templatesDir = path.resolve( 99 | __dirname, 100 | '..', 101 | 'server', 102 | 'resources', 103 | 'templates' 104 | ); 105 | 106 | export const loadPresetWorkflowTemplates = () => 107 | fs 108 | .readdirSync(templatesDir) 109 | .filter((file) => file.endsWith('.json')) 110 | .map((file) => 111 | JSON.parse(fs.readFileSync(path.join(templatesDir, file), 'utf8')) 112 | ); 113 | 114 | export const resizeObserverMock = jest.fn().mockImplementation(() => ({ 115 | observe: jest.fn(), 116 | unobserve: jest.fn(), 117 | disconnect: jest.fn(), 118 | })); 119 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "skipLibCheck": true, 5 | "baseUrl": ".", 6 | "paths": { 7 | // Allows for importing from `opensearch-dashboards` package for the exported types. 8 | "opensearch-dashboards": ["./opensearch_dashboards"], 9 | "ui/*": ["src/legacy/ui/public/*"], 10 | "test_utils/*": ["src/test_utils/public/*"] 11 | }, 12 | // Support .tsx files and transform JSX into calls to React.createElement 13 | "jsx": "react", 14 | // Enables all strict type checking options. 15 | "strict": true, 16 | // enables "core language features" 17 | "lib": [ 18 | // ESNext auto includes previous versions all the way back to es5 19 | "esnext", 20 | // includes support for browser APIs 21 | "dom" 22 | ], 23 | // Node 8 should support everything output by esnext, we override this 24 | // in webpack with loader-level compiler options 25 | "target": "esnext", 26 | // Use commonjs for node, overridden in webpack to keep import statements 27 | // to maintain support for things like `await import()` 28 | "module": "commonjs", 29 | // Allows default imports from modules with no default export. This does not affect code emit, just type checking. 30 | // We have to enable this option explicitly since `esModuleInterop` doesn't enable it automatically when ES2015 or 31 | // ESNext module format is used. 32 | "allowSyntheticDefaultImports": true, 33 | // Emits __importStar and __importDefault helpers for runtime babel ecosystem compatibility. 34 | "esModuleInterop": true, 35 | // Resolve modules in the same way as Node.js. Aka make `require` works the 36 | // same in TypeScript as it does in Node.js. 37 | "moduleResolution": "node", 38 | // Disallow inconsistently-cased references to the same file. 39 | "forceConsistentCasingInFileNames": true, 40 | // Disable the breaking keyof behaviour introduced in TS 2.9.2 until EUI is updated to support that too 41 | "keyofStringsOnly": true, 42 | // Forbid unused local variables as the rule was deprecated by ts-lint 43 | "noUnusedLocals": true, 44 | // Provide full support for iterables in for..of, spread and destructuring when targeting ES5 or ES3. 45 | "downlevelIteration": true, 46 | // import tslib helpers rather than inlining helpers for iteration or spreading, for instance 47 | "importHelpers": true, 48 | // adding global typings 49 | "types": ["node", "jest", "react"] 50 | }, 51 | "include": [ 52 | "index.ts", 53 | "common/**/*.ts", 54 | "public/**/*.ts", 55 | "public/**/*.tsx", 56 | "server/**/*.ts", 57 | "test/**/*", 58 | "../../typings/**/*" 59 | ], 60 | "exclude": ["node_modules", "*/node_modules/"] 61 | } 62 | --------------------------------------------------------------------------------