From c6ee207837dd410574a9b76eaf8d0b68d0874a05 Mon Sep 17 00:00:00 2001 From: Sachin Chaurasiya Date: Sat, 10 Aug 2024 10:13:24 +0530 Subject: [PATCH] minor: add encoding and decoding for policy rule name (#17344) * minor: add encoding and decoding for policy rule name * move encoding logic to util method * refactor: move the constant to separate file * chore: Remove unnecessary wait for timeout in Policies.spec.ts --- .../ui/cypress/e2e/Pages/Policies.spec.ts | 374 ------------------ .../ui/playwright/constant/permission.ts | 45 +++ .../ui/playwright/e2e/Pages/Policies.spec.ts | 290 ++++++++++++++ .../resources/ui/playwright/utils/form.ts | 51 +++ .../resources/ui/src/hooks/useFqn.test.ts | 3 + .../src/main/resources/ui/src/hooks/useFqn.ts | 5 +- .../PoliciesDetailPage/EditRulePage.tsx | 5 +- .../resources/ui/src/utils/RouterUtils.ts | 3 +- 8 files changed, 396 insertions(+), 380 deletions(-) delete mode 100644 openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Policies.spec.ts create mode 100644 openmetadata-ui/src/main/resources/ui/playwright/constant/permission.ts create mode 100644 openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Policies.spec.ts create mode 100644 openmetadata-ui/src/main/resources/ui/playwright/utils/form.ts diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Policies.spec.ts b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Policies.spec.ts deleted file mode 100644 index 360bb912905..00000000000 --- a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Policies.spec.ts +++ /dev/null @@ -1,374 +0,0 @@ -/* - * Copyright 2022 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 { - descriptionBox, - interceptURL, - uuid, - verifyResponseStatusCode, -} from '../../common/common'; -import { validateFormNameFieldInput } from '../../common/Utils/Form'; -import { BASE_URL } from '../../constants/constants'; -import { GlobalSettingOptions } from '../../constants/settings.constant'; - -const roles = { - dataConsumer: 'Data Consumer', - dataSteward: 'Data Steward', -}; - -const policies = { - dataConsumerPolicy: 'Data Consumer Policy', - dataStewardPolicy: 'Data Steward Policy', - organizationPolicy: 'Organization Policy', - teamOnlyAccessPolicy: 'Team only access Policy', -}; - -const ruleDetails = { - resources: 'All', - operations: 'All', - effect: 'Allow', - condition: 'isOwner()', - inValidCondition: 'isOwner(', -}; - -const errorMessageValidation = { - lastPolicyCannotBeRemoved: 'At least one policy is required in a role', - lastRuleCannotBeRemoved: 'At least one rule is required in a policy', -}; - -const policyName = `Policy-test-${uuid()}`; -const description = `This is ${policyName} description`; - -const ruleName = `Rule-test-${uuid()}`; -const ruleDescription = `This is ${ruleName} description`; -const updatedDescription = 'This is updated description'; - -const newRuleName = `New-Rule-test-${uuid()}`; -const newRuledescription = `This is ${newRuleName} description`; - -const updatedRuleName = `New-Rule-test-${uuid()}-updated`; - -const addRule = (rulename, ruleDescription, descriptionIndex) => { - validateFormNameFieldInput({ - value: rulename, - fieldName: 'Name', - fieldSelector: '[data-testid="rule-name"]', - errorDivSelector: '#ruleName_help', - }); - // Enter rule description - cy.get('.toastui-editor-md-container > .toastui-editor > .ProseMirror') - .eq(descriptionIndex) - .scrollIntoView() - .type(ruleDescription); - // Select resource dropdown - cy.get('[data-testid="resources"]') - .scrollIntoView() - .should('be.visible') - .click(); - - // Select All - cy.get('.ant-select-tree-checkbox-inner').should('be.visible').click(); - - // Click on operations dropdown - cy.get('[data-testid="operations"]').should('be.visible').click(); - - cy.get('.ant-select-tree-checkbox-inner').eq(1).should('be.visible').click(); - // Click on condition combobox - - cy.get('[data-testid="condition"]') - .scrollIntoView() - .should('be.visible') - .click(); - - cy.get(`[title="${ruleDetails.condition}"]`).should('be.visible').click(); - - cy.get('[data-testid="condition-success"]').contains('✅ Valid condition'); - - cy.wait(500); - // Submit - cy.get('[data-testid="submit-btn"]') - .scrollIntoView() - .should('be.visible') - .click(); -}; - -describe('Policy page should work properly', { tags: 'Settings' }, () => { - beforeEach(() => { - cy.login(); - cy.intercept('GET', '*api/v1/policies*').as('getPolicies'); - - cy.settingClick(GlobalSettingOptions.POLICIES); - - cy.wait('@getPolicies', { timeout: 15000 }) - .its('response.statusCode') - .should('equal', 200); - - cy.url().should('eq', `${BASE_URL}/settings/access/policies`); - }); - - it('Default Policies and Roles should be displayed', () => { - // Verifying the default roles and policies are present - - Object.values(policies).forEach((policy) => { - cy.get('[data-testid="policy-name"]') - .should('contain', policy) - .should('be.visible'); - }); - }); - - it('Add new policy', () => { - // Click on add new policy - cy.get('[data-testid="add-policy"]').should('be.visible').click(); - cy.get('[data-testid="inactive-link"]'); - - // Enter policy name - cy.get('[data-testid="policy-name"]').should('be.visible').type(policyName); - validateFormNameFieldInput({ - value: policyName, - fieldName: 'Name', - fieldSelector: '[data-testid="policy-name"]', - errorDivSelector: '#name_help', - }); - - // Enter description - cy.get(descriptionBox).eq(0).type(description); - - // Enter rule name - addRule(ruleName, ruleDescription, 1); - - // Validate the added policy - cy.get('[data-testid="inactive-link"]') - .should('be.visible') - .should('have.text', policyName); - - cy.get('[data-testid="rule-name"]') - .should('be.visible') - .should('contain', ruleName); - - // Verify policy description - cy.get( - '[data-testid="asset-description-container"] [data-testid="viewer-container"]' - ) - .eq(0) - .should('be.visible') - .should('contain', description); - - // verify rule description - cy.get('[data-testid="viewer-container"] > [data-testid="markdown-parser"]') - .should('be.visible') - .should('contain', ruleDescription); - - // Verify other details - cy.get('[data-testid="rule-name"]').should('be.visible').click(); - - cy.get('[data-testid="resources"]') - .should('be.visible') - .should('contain', ruleDetails.resources); - - cy.get('[data-testid="operations"]') - .should('be.visible') - .should('contain', ruleDetails.operations); - - cy.get('[data-testid="effect"]') - .should('be.visible') - .should('contain', ruleDetails.effect); - - cy.get('[data-testid="condition"]') - .should('be.visible') - .should('contain', ruleDetails.condition); - }); - - it('Edit policy description', () => { - interceptURL( - 'GET', - `/api/v1/policies/name/${policyName}*`, - 'getSelectedPolicy' - ); - // Click on created policy name - cy.get('[data-testid="policy-name"]').contains(policyName).click(); - verifyResponseStatusCode('@getSelectedPolicy', 200); - cy.get('[data-testid="edit-description"]').should('be.visible').click(); - // Enter updated description - cy.get(descriptionBox) - .should('be.visible') - .clear() - .type(`${updatedDescription}-${policyName}`); - // Click on save - cy.get('[data-testid="save"]').should('be.visible').click(); - - // Validate added description - cy.get( - '[data-testid="asset-description-container"] [data-testid="viewer-container"]' - ) - .should('be.visible') - .should('contain', `${updatedDescription}-${policyName}`); - }); - - it('Add new rule', () => { - interceptURL( - 'GET', - `/api/v1/policies/name/${policyName}*`, - 'getSelectedPolicy' - ); - // Click on created policy name - cy.get('[data-testid="policy-name"]').contains(policyName).click(); - verifyResponseStatusCode('@getSelectedPolicy', 200); - - interceptURL('GET', '/api/v1/policies/*', 'addRulepage'); - // Click on add rule button - cy.get('[data-testid="add-rule"]').should('be.visible').click(); - - verifyResponseStatusCode('@addRulepage', 200); - - addRule(newRuleName, newRuledescription, 0); - - // Validate added rule - cy.get('[data-testid="rule-name"]') - .should('be.visible') - .should('contain', ruleName); - - // Verify other details - cy.get('[data-testid="rule-name"]') - .last() - .scrollIntoView() - .contains(ruleName) - .should('be.visible') - .click(); - - cy.get('[data-testid="resources"]') - .last() - .scrollIntoView() - .should('exist') - .should('contain', ruleDetails.resources); - - cy.get('[data-testid="operations"]') - .last() - .scrollIntoView() - .should('exist') - .should('contain', ruleDetails.operations); - - cy.get('[data-testid="effect"]') - .last() - .scrollIntoView() - .should('exist') - .should('contain', ruleDetails.effect); - - cy.get('[data-testid="condition"]') - .last() - .scrollIntoView() - .should('exist') - .should('contain', ruleDetails.condition); - }); - - it('Edit rule name for created Rule', () => { - interceptURL( - 'GET', - `/api/v1/policies/name/${policyName}*`, - 'getSelectedPolicy' - ); - // Click on created policy name - cy.get('[data-testid="policy-name"]').contains(policyName).click(); - - verifyResponseStatusCode('@getSelectedPolicy', 200); - // Click on new rule manage button - cy.get(`[data-testid="manage-button-${newRuleName}"]`) - .should('be.visible') - .click(); - - interceptURL('GET', '/api/v1/policies/*', 'editRulePage'); - cy.get('[data-testid="edit-rule"]').should('be.visible').click(); - - verifyResponseStatusCode('@editRulePage', 200); - verifyResponseStatusCode('@getSelectedPolicy', 200); - - // Enter new name - cy.get('[data-testid="rule-name"]').clear().type(updatedRuleName); - - interceptURL('PATCH', '/api/v1/policies/*', 'updateRule'); - - cy.get('[data-testid="submit-btn"]') - .scrollIntoView() - .should('be.visible') - .click(); - - verifyResponseStatusCode('@updateRule', 200); - verifyResponseStatusCode('@getSelectedPolicy', 200); - - cy.url().should('include', policyName); - - cy.get('[data-testid="rule-name"]').should('contain', updatedRuleName); - }); - - it('Delete new rule', () => { - interceptURL( - 'GET', - `/api/v1/policies/name/${policyName}*`, - 'getSelectedPolicy' - ); - // Click on created policy name - cy.get('[data-testid="policy-name"]').contains(policyName).click(); - - verifyResponseStatusCode('@getSelectedPolicy', 200); - - // Click on new rule manage button - cy.get(`[data-testid="manage-button-${updatedRuleName}"]`) - .should('be.visible') - .click(); - - cy.get('[data-testid="delete-rule"]').should('be.visible').click(); - - // Validate the deleted rule - cy.get('[data-testid="rule-name"]') - .should('be.visible') - .should('not.contain', updatedRuleName); - }); - - it('Delete last rule and validate', () => { - interceptURL( - 'GET', - `/api/v1/policies/name/${policyName}*`, - 'getSelectedPolicy' - ); - // Click on created policy name - cy.get('[data-testid="policy-name"]').contains(policyName).click(); - - verifyResponseStatusCode('@getSelectedPolicy', 200); - - // Click on new rule manage button - cy.get(`[data-testid="manage-button-${ruleName}"]`) - .should('be.visible') - .click(); - interceptURL('PATCH', '/api/v1/policies/*', 'deletelastPolicy'); - - cy.get('[data-testid="delete-rule"]').should('be.visible').click(); - - verifyResponseStatusCode('@deletelastPolicy', 400); - - cy.get('.Toastify__toast-body') - .should('be.visible') - .should('contain', errorMessageValidation.lastRuleCannotBeRemoved); - }); - - it('Delete created policy', () => { - cy.get(`[data-testid="delete-action-${policyName}"]`).click({ - force: true, - }); - - cy.get('[data-testid="confirmation-text-input"]').type('DELETE'); - - cy.get('[data-testid="confirm-button"]').click(); - - // Validate deleted policy - cy.get('[data-testid="policy-name"]').should('not.contain', policyName); - }); -}); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/constant/permission.ts b/openmetadata-ui/src/main/resources/ui/playwright/constant/permission.ts new file mode 100644 index 00000000000..81ddb6f7962 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/playwright/constant/permission.ts @@ -0,0 +1,45 @@ +/* + * 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 { uuid } from '../utils/common'; + +export const DEFAULT_POLICIES = { + dataConsumerPolicy: 'Data Consumer Policy', + dataStewardPolicy: 'Data Steward Policy', + organizationPolicy: 'Organization Policy', + teamOnlyAccessPolicy: 'Team only access Policy', +}; + +export const RULE_DETAILS = { + resources: 'All', + operations: 'All', + effect: 'Allow', + condition: 'isOwner()', + inValidCondition: 'isOwner(', +}; + +export const ERROR_MESSAGE_VALIDATION = { + lastPolicyCannotBeRemoved: 'At least one policy is required in a role', + lastRuleCannotBeRemoved: 'At least one rule is required in a policy', +}; + +export const POLICY_NAME = `Policy-test-${uuid()}`; +export const DESCRIPTION = `This is ${POLICY_NAME} description`; + +export const RULE_NAME = `Rule / test-${uuid()}`; +export const RULE_DESCRIPTION = `This is ${RULE_NAME} description`; +export const UPDATED_DESCRIPTION = 'This is updated description'; + +export const NEW_RULE_NAME = `New / Rule-test-${uuid()}`; +export const NEW_RULE_DESCRIPTION = `This is ${NEW_RULE_NAME} description`; + +export const UPDATED_RULE_NAME = `New-Rule-test-${uuid()}-updated`; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Policies.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Policies.spec.ts new file mode 100644 index 00000000000..6944f6db872 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Policies.spec.ts @@ -0,0 +1,290 @@ +/* + * 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 { + DEFAULT_POLICIES, + DESCRIPTION, + ERROR_MESSAGE_VALIDATION, + NEW_RULE_DESCRIPTION, + NEW_RULE_NAME, + POLICY_NAME, + RULE_DESCRIPTION, + RULE_DETAILS, + RULE_NAME, + UPDATED_DESCRIPTION, + UPDATED_RULE_NAME, +} from '../../constant/permission'; +import { GlobalSettingOptions } from '../../constant/settings'; +import { descriptionBox, redirectToHomePage } from '../../utils/common'; +import { validateFormNameFieldInput } from '../../utils/form'; +import { settingClick } from '../../utils/sidebar'; + +// use the admin user to login +test.use({ storageState: 'playwright/.auth/admin.json' }); + +const addRule = async ( + page: Page, + rulename: string, + ruleDescription: string, + descriptionIndex: number +) => { + // Validate form name field input + await page.fill('[data-testid="rule-name"]', rulename); + + // Enter rule description + await page + .locator(descriptionBox) + .nth(descriptionIndex) + .fill(ruleDescription); + + // Select resource dropdown + await page.locator('[data-testid="resources"]').click(); + + // Select All + await page.locator('.ant-select-tree-checkbox-inner').first().click(); + + // Click on operations dropdown + await page.locator('[data-testid="operations"]').click(); + + // Select operation + await page.locator('.ant-select-tree-checkbox-inner').nth(1).click(); + + // Click on condition combobox + await page.locator('[data-testid="condition"]').click(); + + // Select condition + await page.locator(`[title="${RULE_DETAILS.condition}"]`).click(); + + // Verify condition success + await expect(page.locator('[data-testid="condition-success"]')).toContainText( + '✅ Valid condition' + ); + + // Submit + await page.locator('[data-testid="submit-btn"]').click(); +}; + +test.describe('Policy page should work properly', () => { + test.beforeEach('Visit entity details page', async ({ page }) => { + await redirectToHomePage(page); + await settingClick(page, GlobalSettingOptions.POLICIES); + await page.waitForSelector('[data-testid="loader"]', { state: 'detached' }); + }); + + test('Add new policy with invalid condition', async ({ page }) => { + await test.step( + 'Default Policies and Roles should be displayed', + async () => { + // Verifying the default roles and policies are present + for (const policy of Object.values(DEFAULT_POLICIES)) { + const policyElement = page.locator('[data-testid="policy-name"]', { + hasText: policy, + }); + + await expect(policyElement).toBeVisible(); + } + } + ); + + await test.step('Add new policy', async () => { + // Click on add new policy + await page.locator('[data-testid="add-policy"]').click(); + + await expect(page.locator('[data-testid="inactive-link"]')).toBeVisible(); + + // Enter policy name + await page.locator('[data-testid="policy-name"]').fill(POLICY_NAME); + await validateFormNameFieldInput({ + page, + value: POLICY_NAME, + fieldName: 'Name', + fieldSelector: '[data-testid="policy-name"]', + errorDivSelector: '#name_help', + }); + + // Enter description + await page.locator(descriptionBox).nth(0).fill(DESCRIPTION); + + // Enter rule name + await addRule(page, RULE_NAME, RULE_DESCRIPTION, 1); + + // Validate the added policy + await expect(page.locator('[data-testid="inactive-link"]')).toHaveText( + POLICY_NAME + ); + + await page.getByText(RULE_NAME, { exact: true }).isVisible(); + + // Verify policy description + await expect( + page + .locator( + '[data-testid="asset-description-container"] [data-testid="viewer-container"]' + ) + .nth(0) + ).toContainText(DESCRIPTION); + + // Verify rule description + await expect( + page + .locator( + '[data-testid="viewer-container"] > [data-testid="markdown-parser"]' + ) + .nth(1) + ).toContainText(RULE_DESCRIPTION); + + // Verify other details + await page.locator('[data-testid="rule-name"]').click(); + + await expect(page.locator('[data-testid="resources"]')).toContainText( + RULE_DETAILS.resources + ); + + await expect(page.locator('[data-testid="operations"]')).toContainText( + RULE_DETAILS.operations + ); + + await expect(page.locator('[data-testid="effect"]')).toContainText( + RULE_DETAILS.effect + ); + + await expect(page.locator('[data-testid="condition"]')).toContainText( + RULE_DETAILS.condition + ); + }); + + await test.step('Edit policy description', async () => { + // Click on edit description + await page.locator('[data-testid="edit-description"]').click(); + + await page + .locator(descriptionBox) + .fill(`${UPDATED_DESCRIPTION}-${POLICY_NAME}`); + + // Click on save + await page.locator('[data-testid="save"]').click(); + + // Validate added description + await expect( + page + .locator( + '[data-testid="asset-description-container"] [data-testid="viewer-container"]' + ) + .nth(0) + ).toContainText(`${UPDATED_DESCRIPTION}-${POLICY_NAME}`); + }); + + await test.step('Add new rule', async () => { + // Click on add rule button + await page.locator('[data-testid="add-rule"]').click(); + + // Add rule (assuming addRule is a function you have defined elsewhere) + await addRule(page, NEW_RULE_NAME, NEW_RULE_DESCRIPTION, 0); + + // Validate added rule + await page.getByText(RULE_NAME, { exact: true }).isVisible(); + + // Verify other details + await page.getByText(RULE_NAME, { exact: true }).click(); + + await expect( + page.locator('[data-testid="resources"]').last() + ).toContainText(RULE_DETAILS.resources); + + await expect( + page.locator('[data-testid="operations"]').last() + ).toContainText(RULE_DETAILS.operations); + + await expect(page.locator('[data-testid="effect"]').last()).toContainText( + RULE_DETAILS.effect + ); + + await expect( + page.locator('[data-testid="condition"]').last() + ).toContainText(RULE_DETAILS.condition); + }); + + await test.step('Edit rule name for created Rule', async () => { + // Click on new rule manage button + await page + .locator(`[data-testid="manage-button-${NEW_RULE_NAME}"]`) + .click(); + + // Click on edit rule button + await page.locator('[data-testid="edit-rule"]').click(); + + // Enter new name + await page.locator('[data-testid="rule-name"]').fill(UPDATED_RULE_NAME); + + // Click on submit button + await page.locator('[data-testid="submit-btn"]').click(); + + // Verify the URL contains the policy name + await expect(page).toHaveURL(new RegExp(POLICY_NAME)); + + // Verify the rule name is updated + await page.getByText(UPDATED_RULE_NAME, { exact: true }).isVisible(); + }); + + await test.step('Delete new rule', async () => { + // Click on new rule manage button + await page + .locator(`[data-testid="manage-button-${UPDATED_RULE_NAME}"]`) + .click(); + + // Click on delete rule button + await page.locator('[data-testid="delete-rule"]').click(); + + await expect( + page.getByText(UPDATED_RULE_NAME, { exact: true }) + ).not.toBeVisible(); + }); + + await test.step('Delete last rule and validate', async () => { + // Click on new rule manage button + await page.locator(`[data-testid="manage-button-${RULE_NAME}"]`).click(); + + // Click on delete rule button + await page.locator('[data-testid="delete-rule"]').click(); + + // Validate the error message + await expect(page.locator('.Toastify__toast-body')).toContainText( + ERROR_MESSAGE_VALIDATION.lastRuleCannotBeRemoved + ); + }); + + await test.step('Delete created policy', async () => { + await settingClick(page, GlobalSettingOptions.POLICIES); + await page.waitForSelector('[data-testid="loader"]', { + state: 'detached', + }); + // Click on delete action button + await page + .locator(`[data-testid="delete-action-${POLICY_NAME}"]`) + .click({ force: true }); + + // Type 'DELETE' in the confirmation text input + await page + .locator('[data-testid="confirmation-text-input"]') + .fill('DELETE'); + + // Click on confirm button + await page.locator('[data-testid="confirm-button"]').click(); + + // Validate deleted policy + await expect( + page.getByText(POLICY_NAME, { exact: true }) + ).not.toBeVisible(); + }); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/form.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/form.ts new file mode 100644 index 00000000000..20b044e0472 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/form.ts @@ -0,0 +1,51 @@ +/* + * 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 { Page } from '@playwright/test'; + +export const validateFormNameFieldInput = async ({ + page, + fieldSelector = '#name', + checkEmpty = true, + checkLong = true, + fieldName, + value, +}: { + page: Page; + value: string; + fieldName: string; + errorDivSelector: string; + fieldSelector?: string; + checkEmpty?: boolean; + checkLong?: boolean; +}) => { + if (checkEmpty) { + // Check empty name field message + await page.fill(fieldSelector, 'test'); + await page.locator(fieldSelector).clear(); + + await page.getByText(`${fieldName} is required`).isVisible(); + } + + if (checkLong) { + // Check long name field message + await page.fill(fieldSelector, 'name'.repeat(33)); + + await page + .getByText(`${fieldName} size must be between 1 and 128`) + .isVisible(); + + await page.locator(fieldSelector).clear(); + } + + await page.fill(fieldSelector, value); +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/hooks/useFqn.test.ts b/openmetadata-ui/src/main/resources/ui/src/hooks/useFqn.test.ts index c9722711436..ce0887633a2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/hooks/useFqn.test.ts +++ b/openmetadata-ui/src/main/resources/ui/src/hooks/useFqn.test.ts @@ -27,6 +27,7 @@ describe('useFqn', () => { (useParams as jest.Mock).mockReturnValue({ fqn: 'sample_data.db_sample.schema_sample.dim%2Fclient.', ingestionFQN: 'sample_data.db_sample.schema_sample.dim%2Fclient.', + ruleName: 'testing%20%2F%20policy%20rule%20do%20not%20use', }); const { result } = renderHook(() => useFqn()); @@ -34,6 +35,7 @@ describe('useFqn', () => { expect(result.current).toEqual({ fqn: 'sample_data.db_sample.schema_sample.dim/client.', ingestionFQN: 'sample_data.db_sample.schema_sample.dim/client.', + ruleName: 'testing / policy rule do not use', }); }); @@ -45,6 +47,7 @@ describe('useFqn', () => { expect(result.current).toEqual({ fqn: '', ingestionFQN: '', + ruleName: '', }); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/hooks/useFqn.ts b/openmetadata-ui/src/main/resources/ui/src/hooks/useFqn.ts index aca6e6aeab5..c217177405c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/hooks/useFqn.ts +++ b/openmetadata-ui/src/main/resources/ui/src/hooks/useFqn.ts @@ -13,17 +13,18 @@ import { useParams } from 'react-router-dom'; import { getDecodedFqn } from '../utils/StringsUtils'; -type Fqn = { fqn: string; ingestionFQN: string }; +type Fqn = { fqn: string; ingestionFQN: string; ruleName: string }; /** * @description Hook to get the decoded fqn and ingestionFQN from the url * @returns {fqn: string, ingestionFQN: string} - fqn and ingestionFQN from the url */ export const useFqn = (): Fqn => { - const { fqn, ingestionFQN } = useParams(); + const { fqn, ingestionFQN, ruleName } = useParams(); return { fqn: fqn ? getDecodedFqn(fqn) : '', ingestionFQN: ingestionFQN ? getDecodedFqn(ingestionFQN) : '', + ruleName: ruleName ? getDecodedFqn(ruleName) : '', }; }; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/PoliciesPage/PoliciesDetailPage/EditRulePage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/PoliciesPage/PoliciesDetailPage/EditRulePage.tsx index b67e5423f99..bc3f723a457 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/PoliciesPage/PoliciesDetailPage/EditRulePage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/PoliciesPage/PoliciesDetailPage/EditRulePage.tsx @@ -17,7 +17,7 @@ import { compare } from 'fast-json-patch'; import { trim } from 'lodash'; import React, { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useHistory, useParams } from 'react-router-dom'; +import { useHistory } from 'react-router-dom'; import Loader from '../../../components/common/Loader/Loader'; import TitleBreadcrumb from '../../../components/common/TitleBreadcrumb/TitleBreadcrumb.component'; import PageLayoutV1 from '../../../components/PageLayoutV1/PageLayoutV1'; @@ -51,8 +51,7 @@ const InitialData: Rule = { const EditRulePage = () => { const { t } = useTranslation(); const history = useHistory(); - const { ruleName } = useParams<{ ruleName: string }>(); - const { fqn } = useFqn(); + const { fqn, ruleName } = useFqn(); const [isLoading, setLoading] = useState(false); const [policy, setPolicy] = useState({} as Policy); const [ruleData, setRuleData] = useState(InitialData); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/RouterUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/RouterUtils.ts index 6f45b819f8b..f9b0d94edae 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/RouterUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/RouterUtils.ts @@ -323,7 +323,8 @@ export const getEditPolicyRulePath = (fqn: string, ruleName: string) => { path = path .replace(PLACEHOLDER_ROUTE_FQN, getEncodedFqn(fqn)) - .replace(PLACEHOLDER_RULE_NAME, ruleName); + // rule name is same as entity fqn so we need to encode it to pass it as a param + .replace(PLACEHOLDER_RULE_NAME, getEncodedFqn(ruleName)); return path; };