diff --git a/datahub-web-react/src/app/ingest/ManageIngestionPage.tsx b/datahub-web-react/src/app/ingest/ManageIngestionPage.tsx index 1d04edbac228a..6af924be99a6a 100644 --- a/datahub-web-react/src/app/ingest/ManageIngestionPage.tsx +++ b/datahub-web-react/src/app/ingest/ManageIngestionPage.tsx @@ -2,6 +2,8 @@ import { Tabs, Typography } from 'antd'; import React, { useState } from 'react'; import styled from 'styled-components'; import { IngestionSourceList } from './source/IngestionSourceList'; +import { useAppConfig } from '../useAppConfig'; +import { useUserContext } from '../context/useUserContext'; import { SecretsList } from './secret/SecretsList'; import { OnboardingTour } from '../onboarding/OnboardingTour'; import { @@ -48,7 +50,13 @@ export const ManageIngestionPage = () => { /** * Determines which view should be visible: ingestion sources or secrets. */ - const [selectedTab, setSelectedTab] = useState(TabType.Sources); + const me = useUserContext(); + const { config } = useAppConfig(); + const isIngestionEnabled = config?.managedIngestionConfig.enabled; + const showIngestionTab = isIngestionEnabled && me && me.platformPrivileges?.manageIngestion; + const showSecretsTab = isIngestionEnabled && me && me.platformPrivileges?.manageSecrets; + const defaultTab = showIngestionTab ? TabType.Sources : TabType.Secrets; + const [selectedTab, setSelectedTab] = useState(defaultTab); const onClickTab = (newTab: string) => { setSelectedTab(TabType[newTab]); @@ -64,8 +72,8 @@ export const ManageIngestionPage = () => { onClickTab(tab)}> - - + {showIngestionTab && } + {showSecretsTab && } {selectedTab === TabType.Sources ? : } diff --git a/datahub-web-react/src/app/ingest/secret/SecretsList.tsx b/datahub-web-react/src/app/ingest/secret/SecretsList.tsx index 2219b6147d9e0..472dbf7f849de 100644 --- a/datahub-web-react/src/app/ingest/secret/SecretsList.tsx +++ b/datahub-web-react/src/app/ingest/secret/SecretsList.tsx @@ -118,7 +118,7 @@ export const SecretsList = () => { { urn: res.data?.createSecret || '', name: state.name, - description: state.description, + description: state.description || '', }, client, pageSize, @@ -127,7 +127,7 @@ export const SecretsList = () => { .catch((e) => { message.destroy(); message.error({ - content: `Failed to update ingestion source!: \n ${e.message || ''}`, + content: `Failed to update secret!: \n ${e.message || ''}`, duration: 3, }); }); diff --git a/datahub-web-react/src/app/shared/admin/HeaderLinks.tsx b/datahub-web-react/src/app/shared/admin/HeaderLinks.tsx index e7b0025118ff1..cce2a2336515d 100644 --- a/datahub-web-react/src/app/shared/admin/HeaderLinks.tsx +++ b/datahub-web-react/src/app/shared/admin/HeaderLinks.tsx @@ -75,7 +75,7 @@ export function HeaderLinks(props: Props) { const showAnalytics = (isAnalyticsEnabled && me && me?.platformPrivileges?.viewAnalytics) || false; const showSettings = true; const showIngestion = - isIngestionEnabled && me && me.platformPrivileges?.manageIngestion && me.platformPrivileges?.manageSecrets; + isIngestionEnabled && me && (me.platformPrivileges?.manageIngestion || me.platformPrivileges?.manageSecrets); useToggleEducationStepIdsAllowList(!!showIngestion, HOME_PAGE_INGESTION_ID); diff --git a/smoke-test/tests/cypress/cypress/e2e/mutations/ingestion_source.js b/smoke-test/tests/cypress/cypress/e2e/mutations/ingestion_source.js index 470f9e2eec461..8707f090acad3 100644 --- a/smoke-test/tests/cypress/cypress/e2e/mutations/ingestion_source.js +++ b/smoke-test/tests/cypress/cypress/e2e/mutations/ingestion_source.js @@ -11,6 +11,7 @@ describe("ingestion source creation flow", () => { // Go to ingestion page, create a snowflake source cy.loginWithCredentials(); cy.goToIngestionPage(); + cy.clickOptionWithId('[data-node-key="Sources"]'); cy.clickOptionWithTestId("create-ingestion-source-button"); cy.clickOptionWithText("Snowflake"); cy.waitTextVisible("Snowflake Details"); diff --git a/smoke-test/tests/cypress/cypress/e2e/mutations/manage_ingestion_secret_privilege.js b/smoke-test/tests/cypress/cypress/e2e/mutations/manage_ingestion_secret_privilege.js new file mode 100644 index 0000000000000..a4d1e6ca375a4 --- /dev/null +++ b/smoke-test/tests/cypress/cypress/e2e/mutations/manage_ingestion_secret_privilege.js @@ -0,0 +1,199 @@ +const test_id = Math.floor(Math.random() * 100000); +const platform_policy_name = `Platform test policy ${test_id}`; +const number = Math.floor(Math.random() * 100000); +const name = `Example Name ${number}`; +const email = `example${number}@example.com`; + +const tryToSignUp = () => { + cy.enterTextInTestId("email", email); + cy.enterTextInTestId("name", name); + cy.enterTextInTestId("password", "Example password"); + cy.enterTextInTestId("confirmPassword", "Example password"); + cy.mouseover("#title").click(); + cy.waitTextVisible("Other").click(); + cy.clickOptionWithId("[type=submit]"); + return { name, email }; +}; + +const signIn = () => { + cy.visit("/login"); + cy.enterTextInTestId("username", email); + cy.enterTextInTestId("password", "Example password"); + cy.clickOptionWithId("[type=submit]"); +}; + +const updateAndSave = (Id, groupName, text) => { + cy.clickOptionWithTestId(Id).type(groupName); + cy.get(".rc-virtual-list").contains(text).click({ force: true }); + cy.focused().blur(); +}; + +const clickFocusAndType = (Id, text) => { + cy.clickOptionWithTestId(Id).focused().clear().type(text); +}; + +const clickOnButton = (saveButton) => { + cy.clickOptionWithId(`#${saveButton}`); +}; + +const createPolicy = (description, policyName) => { + clickFocusAndType("policy-description", description); + clickOnButton("nextButton"); + updateAndSave("privileges", "Ingestion", "Manage Metadata Ingestion"); + cy.wait(1000); + clickOnButton("nextButton"); + updateAndSave("users", "All", "All Users"); + clickOnButton("saveButton"); + cy.waitTextVisible("Successfully saved policy."); + cy.ensureTextNotPresent("Successfully saved policy."); + cy.reload(); + searchAndToggleMetadataPolicyStatus(policyName); + cy.get(".ant-table-row-level-0").contains(policyName); +}; + +const searchAndToggleMetadataPolicyStatus = (metadataPolicyName) => { + cy.get('[data-testid="search-input"]').should("be.visible"); + cy.get('[data-testid="search-input"]').last().type(metadataPolicyName); +}; + +const editPolicy = (policyName, type, select) => { + searchAndToggleMetadataPolicyStatus(policyName); + cy.contains("tr", policyName).as("metadataPolicyRow"); + cy.contains("EDIT").click(); + clickOnButton("nextButton"); + cy.clickOptionWithId(".ant-tag-close-icon"); + updateAndSave("privileges", type, select); + clickOnButton("nextButton"); + cy.clickOptionWithId(".ant-tag-close-icon"); + updateAndSave("users", name, name); + clickOnButton("saveButton"); + cy.waitTextVisible("Successfully saved policy."); +}; + +const deactivateExistingAllUserPolicies = () => { + cy.get(".ant-pagination li") + .its("length") + .then((len) => { + const pageCount = len - 2; + for (let page = 1; page <= pageCount; page++) { + cy.get("tbody tr td").should("be.visible"); + cy.get("tbody tr").each(($row) => { + cy.wrap($row) + .find("td") + .eq(3) + .invoke("text") + .then((role) => { + if (role === "All Users") { + cy.wrap($row) + .find("td") + .eq(5) + .find("div button") + .eq(1) + .invoke("text") + .then((buttonText) => { + if (buttonText === "DEACTIVATE") { + cy.wrap($row) + .find("td") + .eq(5) + .find("div button") + .eq(1) + .click(); + cy.waitTextVisible("Successfully deactivated policy."); + } + }); + } + }); + }); + if (page < pageCount) { + cy.contains("li", `${page + 1}`).click(); + cy.ensureTextNotPresent("No Policies"); + } + } + }); +}; + +describe("Manage Ingestion and Secret Privileges", () => { + let registeredEmail = ""; + it("create Metadata Ingestion platform policy and assign privileges to all users", () => { + cy.loginWithCredentials(); + cy.visit("/settings/permissions/policies"); + cy.waitTextVisible("Manage Permissions"); + cy.get(".ant-select-selection-item").should("be.visible").click(); + cy.get(".ant-select-item-option-content").contains("All").click(); + cy.get('[data-icon="delete"]').should("be.visible"); + deactivateExistingAllUserPolicies(); + cy.reload(); + cy.clickOptionWithText("Create new policy"); + clickFocusAndType("policy-name", platform_policy_name); + cy.clickOptionWithId('[data-testid="policy-type"] [title="Metadata"]'); + cy.clickOptionWithTestId("platform"); + createPolicy( + `Platform policy description ${test_id}`, + platform_policy_name, + ); + cy.logout(); + }); + + it("Create user and verify ingestion tab not present", () => { + cy.loginWithCredentials(); + cy.visit("/settings/identities/users"); + cy.waitTextVisible("Invite Users"); + cy.clickOptionWithText("Invite Users"); + cy.waitTextVisible(/signup\?invite_token=\w{32}/).then(($elem) => { + const inviteLink = $elem.text(); + cy.log(inviteLink); + cy.visit("/settings/identities/users"); + cy.logout(); + cy.visit(inviteLink); + const { name, email } = tryToSignUp(); + registeredEmail = email; + cy.waitTextVisible("Welcome to DataHub"); + cy.hideOnboardingTour(); + cy.waitTextVisible(name); + }); + }); + + it("Edit Metadata Ingestion platform policy and assign privileges to the user", () => { + cy.loginWithCredentials(); + cy.visit("/settings/permissions/policies"); + cy.waitTextVisible("Manage Permissions"); + editPolicy(platform_policy_name, "Ingestion", "Manage Metadata Ingestion"); + }); + + it("Verify new user can see ingestion and access Manage Ingestion tab", () => { + cy.clearCookies(); + cy.clearLocalStorage(); + signIn(); + cy.waitTextVisible("Welcome to DataHub"); + cy.hideOnboardingTour(); + cy.waitTextVisible(name); + cy.clickOptionWithText("Ingestion"); + cy.wait(1000); + cy.get("body").click(); + cy.waitTextVisible("Manage Data Sources"); + cy.waitTextVisible("Sources"); + cy.get(".ant-tabs-nav-list").contains("Source").should("be.visible"); + cy.get(".ant-tabs-tab").should("have.length", 1); + }); + + it("Verify new user can see ingestion and access Manage Secret tab", () => { + cy.clearCookies(); + cy.clearLocalStorage(); + cy.loginWithCredentials(); + cy.visit("/settings/permissions/policies"); + cy.waitTextVisible("Manage Permissions"); + editPolicy(platform_policy_name, "Secret", "Manage Secrets"); + cy.logout(); + signIn(); + cy.waitTextVisible("Welcome to DataHub"); + cy.hideOnboardingTour(); + cy.waitTextVisible(name); + cy.clickOptionWithText("Ingestion"); + cy.wait(1000); + cy.clickOptionWithId("body"); + cy.waitTextVisible("Manage Data Sources"); + cy.waitTextVisible("Secrets"); + cy.get(".ant-tabs-nav-list").contains("Secrets").should("be.visible"); + cy.get(".ant-tabs-tab").should("have.length", 1); + }); +}); diff --git a/smoke-test/tests/cypress/cypress/e2e/mutations/managing_secrets.js b/smoke-test/tests/cypress/cypress/e2e/mutations/managing_secrets.js index 1d95c1533c93c..6953fe0494052 100644 --- a/smoke-test/tests/cypress/cypress/e2e/mutations/managing_secrets.js +++ b/smoke-test/tests/cypress/cypress/e2e/mutations/managing_secrets.js @@ -11,7 +11,6 @@ describe("managing secrets for ingestion creation", () => { // Navigate to the manage ingestion page → secrets cy.loginWithCredentials(); cy.goToIngestionPage(); - cy.openEntityTab("Secrets"); // Create a new secret cy.clickOptionWithTestId("create-secret-button"); @@ -28,6 +27,7 @@ describe("managing secrets for ingestion creation", () => { // Create an ingestion source using a secret cy.goToIngestionPage(); + cy.clickOptionWithId('[data-node-key="Sources"]'); cy.get("#ingestion-create-source").click(); cy.clickOptionWithText("Snowflake"); cy.waitTextVisible("Snowflake Details"); @@ -60,10 +60,10 @@ describe("managing secrets for ingestion creation", () => { // Remove ingestion source cy.goToIngestionPage(); - cy.get('[data-testid="delete-button"]').first().click(); + cy.clickOptionWithId('[data-node-key="Sources"]'); + cy.get('[aria-label="delete"]').first().click(); cy.waitTextVisible("Confirm Ingestion Source Removal"); cy.get("button").contains("Yes").click(); - cy.waitTextVisible("Removed ingestion source."); cy.ensureTextNotPresent(ingestion_source_name); // Verify secret is not present during ingestion source creation for password dropdown @@ -99,10 +99,10 @@ describe("managing secrets for ingestion creation", () => { // Remove ingestion source and secret cy.goToIngestionPage(); - cy.get('[data-testid="delete-button"]').first().click(); + cy.clickOptionWithId('[data-node-key="Sources"]'); + cy.get('[aria-label="delete"]').first().click(); cy.waitTextVisible("Confirm Ingestion Source Removal"); cy.get("button").contains("Yes").click(); - cy.waitTextVisible("Removed ingestion source."); cy.ensureTextNotPresent(ingestion_source_name); cy.clickOptionWithText("Secrets"); cy.waitTextVisible(`secretname${number}`);