From 15ae2d3cc3872c6db94529472654236baf64dd1f Mon Sep 17 00:00:00 2001
From: Ashish Gupta
Date: Tue, 9 Jul 2024 14:57:12 +0530
Subject: [PATCH] MINOR: supported all_index in search index configuration form
(#16571)
* supported all_index in search index configuration form
* allow clear in select widget
* supported tree select for the entities
* playwright test
* added env for the run application status test
* fix beta badge color, color of checkbox and changes as per comments
* minor fix
* fix sonar issue
---
.../data/app/SearchIndexingApplication.json | 5 +-
.../SearchIndexingApplication.json | 8 +-
.../e2e/Pages/SearchIndexApplication.spec.ts | 217 ++++++++++++++++++
.../ui/playwright/utils/customProperty.ts | 2 +-
.../AddService/Steps/SelectServiceType.tsx | 8 +-
.../JsonSchemaWidgets/SelectWidget.test.tsx | 42 +++-
.../JsonSchemaWidgets/SelectWidget.tsx | 15 +-
.../TreeSelectWidget.test.tsx | 100 ++++++++
.../JsonSchemaWidgets/TreeSelectWidget.tsx | 72 ++++++
.../ui/src/mocks/SelectWidget.mock.ts | 169 ++++++++++++++
.../ui/src/styles/components/rjsf.less | 2 +
.../SearchIndexingApplication.json | 40 +---
12 files changed, 616 insertions(+), 64 deletions(-)
create mode 100644 openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/SearchIndexApplication.spec.ts
create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/TreeSelectWidget.test.tsx
create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/TreeSelectWidget.tsx
diff --git a/openmetadata-service/src/main/resources/json/data/app/SearchIndexingApplication.json b/openmetadata-service/src/main/resources/json/data/app/SearchIndexingApplication.json
index 5ca7d2ec8217..a9939936e444 100644
--- a/openmetadata-service/src/main/resources/json/data/app/SearchIndexingApplication.json
+++ b/openmetadata-service/src/main/resources/json/data/app/SearchIndexingApplication.json
@@ -29,13 +29,14 @@
"dashboardService",
"pipelineService",
"mlmodelService",
+ "storageService",
+ "metadataService",
"searchService",
"entityReportData",
"webAnalyticEntityViewReportData",
"webAnalyticUserActivityReportData",
"domain",
"storedProcedure",
- "storageService",
"dataProduct",
"testCaseResolutionStatus"
],
@@ -47,4 +48,4 @@
"scheduleTimeline": "Custom",
"cronExpression": "0 0 * * *"
}
-}
\ No newline at end of file
+}
diff --git a/openmetadata-service/src/main/resources/json/data/appMarketPlaceDefinition/SearchIndexingApplication.json b/openmetadata-service/src/main/resources/json/data/appMarketPlaceDefinition/SearchIndexingApplication.json
index a5371efbf407..90d6dbfc4c14 100644
--- a/openmetadata-service/src/main/resources/json/data/appMarketPlaceDefinition/SearchIndexingApplication.json
+++ b/openmetadata-service/src/main/resources/json/data/appMarketPlaceDefinition/SearchIndexingApplication.json
@@ -43,17 +43,19 @@
"dashboardService",
"pipelineService",
"mlmodelService",
+ "storageService",
+ "metadataService",
"searchService",
"entityReportData",
"webAnalyticEntityViewReportData",
"webAnalyticUserActivityReportData",
"domain",
"storedProcedure",
- "storageService",
- "dataProduct"
+ "dataProduct",
+ "testCaseResolutionStatus"
],
"recreateIndex": true,
"batchSize": "100",
"searchIndexMappingLanguage": "EN"
}
-}
\ No newline at end of file
+}
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/SearchIndexApplication.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/SearchIndexApplication.spec.ts
new file mode 100644
index 000000000000..2674df203023
--- /dev/null
+++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/SearchIndexApplication.spec.ts
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2024 Collate.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import test, { expect, Page } from '@playwright/test';
+import { GlobalSettingOptions } from '../../constant/settings';
+import { getApiContext, redirectToHomePage } from '../../utils/common';
+import { settingClick } from '../../utils/sidebar';
+
+// use the admin user to login
+test.use({ storageState: 'playwright/.auth/admin.json' });
+
+const verifyApplicationTriggerToastData = async (page: Page) => {
+ await expect(page.getByRole('alert').first()).toHaveText(
+ /Application triggered successfully/
+ );
+
+ await page.getByLabel('close').first().click();
+};
+
+const verifyLastExecutionStatus = async (page: Page) => {
+ const { apiContext } = await getApiContext(page);
+
+ await expect
+ .poll(
+ async () => {
+ const response = await apiContext
+ .get(
+ '/api/v1/apps/name/SearchIndexingApplication/status?offset=0&limit=1'
+ )
+ .then((res) => res.json());
+
+ return response.data[0]?.status;
+ },
+ {
+ // Custom expect message for reporting, optional.
+ message: 'To get the last run execution status as success',
+ intervals: [30_000],
+ timeout: 300_000,
+ }
+ )
+ .toBe('success');
+
+ await page.reload();
+
+ await page.waitForSelector('[data-testid="app-run-history-table"]');
+
+ await expect(page.getByTestId('pipeline-status')).toContainText('Success');
+};
+
+const verifyLastExecutionRun = async (page: Page) => {
+ const response = await page.waitForResponse(
+ '/api/v1/apps/name/SearchIndexingApplication/status?offset=0&limit=1'
+ );
+
+ expect(response.status()).toBe(200);
+
+ const responseData = await response.json();
+ if (responseData.data.length > 0) {
+ expect(responseData.data).toHaveLength(1);
+
+ if (responseData.data[0].status === 'running') {
+ // wait for success status
+ await verifyLastExecutionStatus(page);
+ } else {
+ expect(responseData.data[0].status).toBe('success');
+ }
+ }
+};
+
+test('Search Index Application', async ({ page }) => {
+ await test.step('Visit Application page', async () => {
+ await redirectToHomePage(page);
+ await settingClick(page, GlobalSettingOptions.APPLICATIONS);
+ });
+
+ await test.step('Verify last execution run', async () => {
+ await page
+ .locator(
+ '[data-testid="search-indexing-application-card"] [data-testid="config-btn"]'
+ )
+ .click();
+ await verifyLastExecutionRun(page);
+ });
+
+ await test.step('Edit application', async () => {
+ await page.click('[data-testid="edit-button"]');
+ await page.click('[data-testid="cron-type"]');
+ await page.click('.rc-virtual-list [title="None"]');
+
+ const deployResponse = page.waitForResponse('/api/v1/apps/*');
+ await page.click('.ant-modal-body [data-testid="deploy-button"]');
+ await deployResponse;
+
+ await expect(page.getByRole('alert').first()).toHaveText(
+ /Schedule saved successfully/
+ );
+
+ await page.getByLabel('close').first().click();
+
+ expect(await page.innerText('[data-testid="schedule-type"]')).toContain(
+ 'None'
+ );
+
+ await page.click('[data-testid="configuration"]');
+ await page.fill('#root\\/batchSize', '0');
+
+ await page.getByTitle('chart').getByLabel('close').click();
+
+ await page.click(
+ '[data-testid="select-widget"] > .ant-select-selector > .ant-select-selection-item'
+ );
+ await page.click('[data-testid="select-option-JP"]');
+
+ const responseAfterSubmit = page.waitForResponse('/api/v1/apps/*');
+ await page.click('[data-testid="submit-btn"]');
+ await responseAfterSubmit;
+
+ await expect(page.getByRole('alert').first()).toHaveText(
+ /Configuration saved successfully/
+ );
+
+ await page.getByLabel('close').first().click();
+ });
+
+ await test.step('Uninstall application', async () => {
+ await page.click('[data-testid="manage-button"]');
+ await page.click('[data-testid="uninstall-button-title"]');
+
+ const deleteRequest = page.waitForResponse(
+ '/api/v1/apps/name/SearchIndexingApplication?hardDelete=true'
+ );
+ await page.click('[data-testid="save-button"]');
+ await deleteRequest;
+
+ await expect(page.getByRole('alert').first()).toHaveText(
+ /Application uninstalled/
+ );
+
+ await page.getByLabel('close').first().click();
+
+ const card1 = page.locator(
+ '[data-testid="search-indexing-application-card"]'
+ );
+
+ expect(await card1.isVisible()).toBe(false);
+ });
+
+ await test.step('Install application', async () => {
+ await page.click('[data-testid="add-application"]');
+
+ // Verify response status code
+ const getMarketPlaceResponse = await page.waitForResponse(
+ '/api/v1/apps/marketplace?limit=*'
+ );
+
+ expect(getMarketPlaceResponse.status()).toBe(200);
+
+ await page.click(
+ '[data-testid="search-indexing-application-card"] [data-testid="config-btn"]'
+ );
+ await page.click('[data-testid="install-application"]');
+ await page.click('[data-testid="save-button"]');
+ await page.click('[data-testid="submit-btn"]');
+ await page.click('[data-testid="cron-type"]');
+ await page.click('.rc-virtual-list [title="None"]');
+
+ expect(await page.innerText('[data-testid="cron-type"]')).toContain('None');
+
+ const installApplicationResponse = page.waitForResponse('api/v1/apps');
+ await page.click('[data-testid="deploy-button"]');
+ await installApplicationResponse;
+
+ await expect(page.getByRole('alert').first()).toHaveText(
+ /Application installed successfully/
+ );
+
+ await page.getByLabel('close').first().click();
+
+ const card = page.locator(
+ '[data-testid="search-indexing-application-card"]'
+ );
+
+ expect(await card.isVisible()).toBe(true);
+ });
+
+ if (process.env.isOss) {
+ await test.step('Run application', async () => {
+ test.slow(true); // Test time shouldn't exceed while re-fetching the history API.
+
+ await page.click(
+ '[data-testid="search-indexing-application-card"] [data-testid="config-btn"]'
+ );
+
+ const triggerPipelineResponse = page.waitForResponse(
+ '/api/v1/apps/trigger/SearchIndexingApplication'
+ );
+ await page.click('[data-testid="run-now-button"]');
+
+ await triggerPipelineResponse;
+
+ await verifyApplicationTriggerToastData(page);
+
+ await page.reload();
+
+ await verifyLastExecutionRun(page);
+ });
+ }
+});
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/customProperty.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/customProperty.ts
index c6cf6b1d9a0c..834dc5ed78c6 100644
--- a/openmetadata-ui/src/main/resources/ui/playwright/utils/customProperty.ts
+++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/customProperty.ts
@@ -12,8 +12,8 @@
*/
import { APIRequestContext, expect, Page } from '@playwright/test';
import {
- ENTITY_PATH,
EntityTypeEndpoint,
+ ENTITY_PATH,
} from '../support/entity/Entity.interface';
import { UserClass } from '../support/user/UserClass';
import { uuid } from './common';
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/AddService/Steps/SelectServiceType.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/AddService/Steps/SelectServiceType.tsx
index e3722d7eeaed..2568a2510bb5 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/AddService/Steps/SelectServiceType.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/AddService/Steps/SelectServiceType.tsx
@@ -28,7 +28,6 @@ import { DatabaseServiceType } from '../../../../../generated/entity/data/databa
import { MetadataServiceType } from '../../../../../generated/entity/services/metadataService';
import { MlModelServiceType } from '../../../../../generated/entity/services/mlmodelService';
import { PipelineServiceType } from '../../../../../generated/entity/services/pipelineService';
-import { useApplicationStore } from '../../../../../hooks/useApplicationStore';
import { errorMsg, getServiceLogo } from '../../../../../utils/CommonUtils';
import ServiceUtilClassBase from '../../../../../utils/ServiceUtilClassBase';
import Searchbar from '../../../../common/SearchBarComponent/SearchBar.component';
@@ -44,7 +43,6 @@ const SelectServiceType = ({
onCancel,
onNext,
}: SelectServiceTypeProps) => {
- const { theme } = useApplicationStore();
const { t } = useTranslation();
const [category, setCategory] = useState('');
const [connectorSearchTerm, setConnectorSearchTerm] = useState('');
@@ -146,11 +144,7 @@ const SelectServiceType = ({
{BETA_SERVICES.includes(
type as DatabaseServiceType | PipelineServiceType
) ? (
-
+
) : null}
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/SelectWidget.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/SelectWidget.test.tsx
index 75f3172b10f6..4467ef5fa39b 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/SelectWidget.test.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/SelectWidget.test.tsx
@@ -22,32 +22,50 @@ import {
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
-import { MOCK_SELECT_WIDGET } from '../../../../../mocks/SelectWidget.mock';
+import {
+ MOCK_SELECT_WIDGET,
+ MOCK_TREE_SELECT_WIDGET,
+} from '../../../../../mocks/SelectWidget.mock';
import SelectWidget from './SelectWidget';
+jest.mock('./TreeSelectWidget', () =>
+ jest.fn().mockImplementation(() => TreeSelectWidget
)
+);
+
const mockOnFocus = jest.fn();
const mockOnBlur = jest.fn();
const mockOnChange = jest.fn();
-const mockProps: WidgetProps = {
+const mockBaseProps = {
onFocus: mockOnFocus,
onBlur: mockOnBlur,
onChange: mockOnChange,
registry: {} as Registry,
+};
+
+const mockSelectProps: WidgetProps = {
+ ...mockBaseProps,
...MOCK_SELECT_WIDGET,
};
+const mockTreeSelectProps: WidgetProps = {
+ ...mockBaseProps,
+ ...MOCK_TREE_SELECT_WIDGET,
+};
+
describe('Test SelectWidget Component', () => {
it('Should render select component', async () => {
- render();
+ render();
const selectInput = screen.getByTestId('select-widget');
+ const treeSelectWidget = screen.queryByText('TreeSelectWidget');
expect(selectInput).toBeInTheDocument();
+ expect(treeSelectWidget).not.toBeInTheDocument();
});
it('Should be disabled', async () => {
- render();
+ render();
const selectInput = await findByRole(
screen.getByTestId('select-widget'),
@@ -58,7 +76,7 @@ describe('Test SelectWidget Component', () => {
});
it('Should call onFocus', async () => {
- render();
+ render();
const selectInput = screen.getByTestId('select-widget');
@@ -68,7 +86,7 @@ describe('Test SelectWidget Component', () => {
});
it('Should call onBlur', async () => {
- render();
+ render();
const selectInput = screen.getByTestId('select-widget');
@@ -78,7 +96,7 @@ describe('Test SelectWidget Component', () => {
});
it('Should call onChange', async () => {
- render();
+ render();
const selectInput = await findByRole(
screen.getByTestId('select-widget'),
@@ -97,4 +115,14 @@ describe('Test SelectWidget Component', () => {
expect(mockOnChange).toHaveBeenCalledTimes(1);
});
+
+ it('Should render TreeSelectWidget component if uiFieldType is treeSelect', async () => {
+ render();
+
+ const selectWidget = screen.queryByTestId('select-widget');
+ const treeSelectWidget = screen.getByText('TreeSelectWidget');
+
+ expect(treeSelectWidget).toBeInTheDocument();
+ expect(selectWidget).not.toBeInTheDocument();
+ });
});
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/SelectWidget.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/SelectWidget.tsx
index 84611eeef4e6..23d6cdbe4ae7 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/SelectWidget.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/SelectWidget.tsx
@@ -14,15 +14,18 @@ import { WidgetProps } from '@rjsf/utils';
import { Select } from 'antd';
import { capitalize } from 'lodash';
import React, { FC } from 'react';
+import TreeSelectWidget from './TreeSelectWidget';
+
+const SelectWidget: FC = (props) => {
+ if (props.schema.uiFieldType === 'treeSelect') {
+ return ;
+ }
+
+ const { onFocus, onBlur, onChange, ...rest } = props;
-const SelectWidget: FC = ({
- onFocus,
- onBlur,
- onChange,
- ...rest
-}) => {
return (