From bd68d959f30601c8decbff45fd69893e7bb1eb3a Mon Sep 17 00:00:00 2001 From: Teddy Date: Mon, 16 Dec 2024 20:57:42 +0100 Subject: [PATCH 1/4] fix: added index on timestamp field for reindex (#19101) --- .../native/1.6.2/mysql/postDataMigrationSQLScript.sql | 0 bootstrap/sql/migrations/native/1.6.2/mysql/schemaChanges.sql | 2 ++ .../native/1.6.2/postgres/postDataMigrationSQLScript.sql | 0 .../sql/migrations/native/1.6.2/postgres/schemaChanges.sql | 2 ++ 4 files changed, 4 insertions(+) create mode 100644 bootstrap/sql/migrations/native/1.6.2/mysql/postDataMigrationSQLScript.sql create mode 100644 bootstrap/sql/migrations/native/1.6.2/mysql/schemaChanges.sql create mode 100644 bootstrap/sql/migrations/native/1.6.2/postgres/postDataMigrationSQLScript.sql create mode 100644 bootstrap/sql/migrations/native/1.6.2/postgres/schemaChanges.sql diff --git a/bootstrap/sql/migrations/native/1.6.2/mysql/postDataMigrationSQLScript.sql b/bootstrap/sql/migrations/native/1.6.2/mysql/postDataMigrationSQLScript.sql new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/bootstrap/sql/migrations/native/1.6.2/mysql/schemaChanges.sql b/bootstrap/sql/migrations/native/1.6.2/mysql/schemaChanges.sql new file mode 100644 index 000000000000..af8cd97bb514 --- /dev/null +++ b/bootstrap/sql/migrations/native/1.6.2/mysql/schemaChanges.sql @@ -0,0 +1,2 @@ +-- add timestamp index for test case result reindex performance +ALTER TABLE data_quality_data_time_series ADD INDEX `idx_timestamp_desc` (timestamp DESC); \ No newline at end of file diff --git a/bootstrap/sql/migrations/native/1.6.2/postgres/postDataMigrationSQLScript.sql b/bootstrap/sql/migrations/native/1.6.2/postgres/postDataMigrationSQLScript.sql new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/bootstrap/sql/migrations/native/1.6.2/postgres/schemaChanges.sql b/bootstrap/sql/migrations/native/1.6.2/postgres/schemaChanges.sql new file mode 100644 index 000000000000..776cc2bd2ee0 --- /dev/null +++ b/bootstrap/sql/migrations/native/1.6.2/postgres/schemaChanges.sql @@ -0,0 +1,2 @@ +-- add timestamp index for test case result reindex performance +CREATE INDEX idx_timestamp_desc ON data_quality_data_time_series (timestamp DESC); From c96c7785df62930a76596845c9289cba60a30aa2 Mon Sep 17 00:00:00 2001 From: Pranita Fulsundar Date: Tue, 17 Dec 2024 10:35:34 +0530 Subject: [PATCH 2/4] Minor: add column name to test case header (#18938) * fix: add column name to test case header * test: add test case for column name * fix: return statement for columnName * refactor: remove replacePlus function call --------- Co-authored-by: Shailesh Parmar --- .../IncidentManagerPageHeader.component.tsx | 27 ++++++++++++++++++- .../IncidentManagerPageHeader.test.tsx | 6 +++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/IncidentManagerPageHeader/IncidentManagerPageHeader.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/IncidentManagerPageHeader/IncidentManagerPageHeader.component.tsx index 49f435ff9c34..c417d45b4b62 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/IncidentManagerPageHeader/IncidentManagerPageHeader.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/IncidentManagerPageHeader/IncidentManagerPageHeader.component.tsx @@ -41,7 +41,10 @@ import { updateTestCaseIncidentById, } from '../../../../rest/incidentManagerAPI'; import { getNameFromFQN } from '../../../../utils/CommonUtils'; -import { getEntityName } from '../../../../utils/EntityUtils'; +import { + getColumnNameFromEntityLink, + getEntityName, +} from '../../../../utils/EntityUtils'; import { getEntityFQN } from '../../../../utils/FeedUtils'; import { checkPermission } from '../../../../utils/PermissionsUtils'; import { getDecodedFqn } from '../../../../utils/StringsUtils'; @@ -76,6 +79,17 @@ const IncidentManagerPageHeader = ({ initialAssignees, } = useActivityFeedProvider(); + const columnName = useMemo(() => { + const isColumn = testCaseData?.entityLink.includes('::columns::'); + if (isColumn) { + const name = getColumnNameFromEntityLink(testCaseData?.entityLink ?? ''); + + return name; + } + + return null; + }, [testCaseData]); + const tableFqn = useMemo( () => getEntityFQN(testCaseData?.entityLink ?? ''), [testCaseData] @@ -327,6 +341,17 @@ const IncidentManagerPageHeader = ({ )} + {columnName && ( + <> + + + {`${t('label.column')}: `} + + {columnName} + + + + )} {`${t('label.test-type')}: `} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/IncidentManagerPageHeader/IncidentManagerPageHeader.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/IncidentManagerPageHeader/IncidentManagerPageHeader.test.tsx index c04596974906..c592351c5de8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/IncidentManagerPageHeader/IncidentManagerPageHeader.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/IncidentManagerPageHeader/IncidentManagerPageHeader.test.tsx @@ -104,6 +104,9 @@ jest.mock('../../../../utils/CommonUtils', () => ({ jest.mock('../../../../utils/EntityUtils', () => ({ getEntityName: jest.fn().mockReturnValue('getEntityName'), + getColumnNameFromEntityLink: jest + .fn() + .mockReturnValue('getColumnNameFromEntityLink'), })); jest.mock('../../../../utils/FeedUtils', () => ({ @@ -270,5 +273,8 @@ describe('Incident Manager Page Header component', () => { // Test Type expect(screen.getByText('label.test-type:')).toBeInTheDocument(); expect(screen.getByText('getEntityName')).toBeInTheDocument(); + // If Column is present + expect(screen.getByText('label.column:')).toBeInTheDocument(); + expect(screen.getByText('getColumnNameFromEntityLink')).toBeInTheDocument(); }); }); From 4b9948dbfbbfcacea997a72df7442b11430a1bb2 Mon Sep 17 00:00:00 2001 From: Mohit Yadav <105265192+mohityadav766@users.noreply.github.com> Date: Tue, 17 Dec 2024 23:43:07 +0530 Subject: [PATCH 3/4] Fix Login Configuration Issue (#19109) * Fix Login Configuration Issue * Fix Tests --- .../exception/CatalogExceptionMessage.java | 2 +- .../service/jdbi3/SystemRepository.java | 5 +++++ .../resources/settings/SettingsCache.java | 2 +- .../security/auth/BasicAuthenticator.java | 14 ++++++-------- .../service/security/auth/LdapAuthenticator.java | 8 +++----- .../service/security/auth/LoginAttemptCache.java | 16 +++++++++++++++- .../resources/system/ConfigResourceTest.java | 2 +- .../resources/system/SystemResourceTest.java | 2 +- 8 files changed, 33 insertions(+), 18 deletions(-) diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/exception/CatalogExceptionMessage.java b/openmetadata-service/src/main/java/org/openmetadata/service/exception/CatalogExceptionMessage.java index 7935488bd8fd..223f2a518961 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/exception/CatalogExceptionMessage.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/exception/CatalogExceptionMessage.java @@ -35,7 +35,7 @@ public final class CatalogExceptionMessage { public static final String PASSWORD_INVALID_FORMAT = "Password must be of minimum 8 characters, with one special, one Upper, one lower case character, and one Digit."; public static final String MAX_FAILED_LOGIN_ATTEMPT = - "Failed Login Attempts Exceeded. Please try after some time."; + "Failed Login Attempts Exceeded. Use Forgot Password or retry after some time."; public static final String INCORRECT_OLD_PASSWORD = "INCORRECT_OLD_PASSWORD"; diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/SystemRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/SystemRepository.java index 302882eb0c7d..01b7d76d12f9 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/SystemRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/SystemRepository.java @@ -43,6 +43,7 @@ import org.openmetadata.service.secrets.SecretsManager; import org.openmetadata.service.secrets.SecretsManagerFactory; import org.openmetadata.service.security.JwtFilter; +import org.openmetadata.service.security.auth.LoginAttemptCache; import org.openmetadata.service.util.JsonUtils; import org.openmetadata.service.util.OpenMetadataConnectionBuilder; import org.openmetadata.service.util.RestUtil; @@ -249,6 +250,10 @@ private void postUpdate(SettingsType settingsType) { WorkflowHandler workflowHandler = WorkflowHandler.getInstance(); workflowHandler.initializeNewProcessEngine(workflowHandler.getProcessEngineConfiguration()); } + + if (settingsType == SettingsType.LOGIN_CONFIGURATION) { + LoginAttemptCache.updateLoginConfiguration(); + } } public void updateSetting(Settings setting) { diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/settings/SettingsCache.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/settings/SettingsCache.java index 16532c563ce7..e68c3b891ae7 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/settings/SettingsCache.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/settings/SettingsCache.java @@ -118,7 +118,7 @@ private static void createDefaultConfiguration(OpenMetadataApplicationConfig app .withConfigValue( new LoginConfiguration() .withMaxLoginFailAttempts(3) - .withAccessBlockTime(600) + .withAccessBlockTime(30) .withJwtTokenExpiryTime(3600)); systemRepository.createNewSetting(setting); } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/security/auth/BasicAuthenticator.java b/openmetadata-service/src/main/java/org/openmetadata/service/security/auth/BasicAuthenticator.java index a99a7eeab87a..9d33d87927d1 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/security/auth/BasicAuthenticator.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/security/auth/BasicAuthenticator.java @@ -102,7 +102,6 @@ public class BasicAuthenticator implements AuthenticatorHandler { private static final int HASHING_COST = 12; private UserRepository userRepository; private TokenRepository tokenRepository; - private LoginAttemptCache loginAttemptCache; private AuthorizerConfiguration authorizerConfiguration; private boolean isSelfSignUpAvailable; @@ -111,7 +110,6 @@ public void init(OpenMetadataApplicationConfig config) { this.userRepository = (UserRepository) Entity.getEntityRepository(Entity.USER); this.tokenRepository = Entity.getTokenRepository(); this.authorizerConfiguration = config.getAuthorizerConfiguration(); - this.loginAttemptCache = new LoginAttemptCache(); this.isSelfSignUpAvailable = config.getAuthenticationConfiguration().getEnableSelfSignup(); } @@ -267,7 +265,7 @@ public void resetUserPasswordWithToken(UriInfo uriInfo, PasswordResetRequest req LOG.error("Error in sending Password Change Mail to User. Reason : " + ex.getMessage(), ex); throw new CustomExceptionMessage(424, FAILED_SEND_EMAIL, EMAIL_SENDING_ISSUE); } - loginAttemptCache.recordSuccessfulLogin(request.getUsername()); + LoginAttemptCache.getInstance().recordSuccessfulLogin(request.getUsername()); } @Override @@ -312,7 +310,7 @@ public void changeUserPwdWithOldPwd( storedUser.getAuthenticationMechanism().setConfig(storedBasicAuthMechanism); PutResponse response = userRepository.createOrUpdate(uriInfo, storedUser); // remove login/details from cache - loginAttemptCache.recordSuccessfulLogin(userName); + LoginAttemptCache.getInstance().recordSuccessfulLogin(userName); // in case admin updates , send email to user if (request.getRequestType() == USER && getSmtpSettings().getEnableSmtpServer()) { @@ -476,7 +474,7 @@ public JwtResponse loginUser(LoginRequest loginRequest) throws IOException, Temp @Override public void checkIfLoginBlocked(String email) { - if (loginAttemptCache.isLoginBlocked(email)) { + if (LoginAttemptCache.getInstance().isLoginBlocked(email)) { throw new AuthenticationException(MAX_FAILED_LOGIN_ATTEMPT); } } @@ -484,15 +482,15 @@ public void checkIfLoginBlocked(String email) { @Override public void recordFailedLoginAttempt(String email, String userName) throws TemplateException, IOException { - loginAttemptCache.recordFailedLogin(email); - int failedLoginAttempt = loginAttemptCache.getUserFailedLoginCount(email); + LoginAttemptCache.getInstance().recordFailedLogin(email); + int failedLoginAttempt = LoginAttemptCache.getInstance().getUserFailedLoginCount(email); if (failedLoginAttempt == SecurityUtil.getLoginConfiguration().getMaxLoginFailAttempts()) { sendAccountStatus( userName, email, "Multiple Failed Login Attempts.", String.format( - "Someone is trying to access your account. Login is Blocked for %s minutes. Please change your password.", + "Someone is trying to access your account. Login is Blocked for %s seconds. Please change your password.", SecurityUtil.getLoginConfiguration().getAccessBlockTime())); } } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/security/auth/LdapAuthenticator.java b/openmetadata-service/src/main/java/org/openmetadata/service/security/auth/LdapAuthenticator.java index 9531d76c1f97..d608434d7b2c 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/security/auth/LdapAuthenticator.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/security/auth/LdapAuthenticator.java @@ -84,7 +84,6 @@ public class LdapAuthenticator implements AuthenticatorHandler { private RoleRepository roleRepository; private UserRepository userRepository; private TokenRepository tokenRepository; - private LoginAttemptCache loginAttemptCache; private LdapConfiguration ldapConfiguration; private LDAPConnectionPool ldapLookupConnectionPool; private boolean isSelfSignUpEnabled; @@ -102,7 +101,6 @@ public void init(OpenMetadataApplicationConfig config) { this.roleRepository = (RoleRepository) Entity.getEntityRepository(Entity.ROLE); this.tokenRepository = Entity.getTokenRepository(); this.ldapConfiguration = config.getAuthenticationConfiguration().getLdapConfiguration(); - this.loginAttemptCache = new LoginAttemptCache(); this.isSelfSignUpEnabled = config.getAuthenticationConfiguration().getEnableSelfSignup(); } @@ -176,7 +174,7 @@ private User checkAndCreateUser(String userDn, String email, String userName) th @Override public void checkIfLoginBlocked(String email) { - if (loginAttemptCache.isLoginBlocked(email)) { + if (LoginAttemptCache.getInstance().isLoginBlocked(email)) { throw new AuthenticationException(MAX_FAILED_LOGIN_ATTEMPT); } } @@ -184,8 +182,8 @@ public void checkIfLoginBlocked(String email) { @Override public void recordFailedLoginAttempt(String email, String userName) throws TemplateException, IOException { - loginAttemptCache.recordFailedLogin(email); - int failedLoginAttempt = loginAttemptCache.getUserFailedLoginCount(email); + LoginAttemptCache.getInstance().recordFailedLogin(email); + int failedLoginAttempt = LoginAttemptCache.getInstance().getUserFailedLoginCount(email); if (failedLoginAttempt == SecurityUtil.getLoginConfiguration().getMaxLoginFailAttempts()) { EmailUtil.sendAccountStatus( userName, diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/security/auth/LoginAttemptCache.java b/openmetadata-service/src/main/java/org/openmetadata/service/security/auth/LoginAttemptCache.java index 0ceb672d098c..336b97e473d1 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/security/auth/LoginAttemptCache.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/security/auth/LoginAttemptCache.java @@ -3,6 +3,7 @@ import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; +import io.dropwizard.logback.shaded.guava.annotations.VisibleForTesting; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import lombok.NonNull; @@ -12,10 +13,11 @@ import org.openmetadata.service.resources.settings.SettingsCache; public class LoginAttemptCache { + private static LoginAttemptCache INSTANCE; private int maxAttempt = 3; private final LoadingCache attemptsCache; - public LoginAttemptCache() { + private LoginAttemptCache() { LoginConfiguration loginConfiguration = SettingsCache.getSetting(SettingsType.LOGIN_CONFIGURATION, LoginConfiguration.class); long accessBlockTime = 600; @@ -35,6 +37,18 @@ public LoginAttemptCache() { }); } + public static LoginAttemptCache getInstance() { + if (INSTANCE == null) { + INSTANCE = new LoginAttemptCache(); + } + return INSTANCE; + } + + public static void updateLoginConfiguration() { + INSTANCE = new LoginAttemptCache(); + } + + @VisibleForTesting public LoginAttemptCache(int maxAttempt, int blockTimeInSec) { this.maxAttempt = maxAttempt; attemptsCache = diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/resources/system/ConfigResourceTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/resources/system/ConfigResourceTest.java index 9cf314ed1755..9a80814133ef 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/resources/system/ConfigResourceTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/resources/system/ConfigResourceTest.java @@ -124,7 +124,7 @@ void get_Login_Configuration_200_OK() throws IOException { LoginConfiguration loginConfiguration = TestUtils.get(target, LoginConfiguration.class, TEST_AUTH_HEADERS); assertEquals(3, loginConfiguration.getMaxLoginFailAttempts()); - assertEquals(600, loginConfiguration.getAccessBlockTime()); + assertEquals(30, loginConfiguration.getAccessBlockTime()); assertEquals(3600, loginConfiguration.getJwtTokenExpiryTime()); } diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/resources/system/SystemResourceTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/resources/system/SystemResourceTest.java index 1cf64c31e5ea..686de7c1bd3a 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/resources/system/SystemResourceTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/resources/system/SystemResourceTest.java @@ -436,7 +436,7 @@ void testLoginConfigurationSettings() throws HttpResponseException { // Assert default values assertEquals(3, loginConfig.getMaxLoginFailAttempts()); - assertEquals(600, loginConfig.getAccessBlockTime()); + assertEquals(30, loginConfig.getAccessBlockTime()); assertEquals(3600, loginConfig.getJwtTokenExpiryTime()); // Update login configuration From e5c27ab8519fe137d7862c2b5200c0807674dc13 Mon Sep 17 00:00:00 2001 From: Shailesh Parmar Date: Thu, 19 Dec 2024 12:41:08 +0530 Subject: [PATCH 4/4] Feat: revamp the permissions in test case and test suite details page --- .../IncidentManagerPageHeader.component.tsx | 29 ++++---- .../TestCaseResultTab.component.tsx | 23 +++--- ...estCaseIncidentManagerStatus.component.tsx | 12 ++- ...TestCaseIncidentManagerStatus.interface.ts | 1 + .../DataQualityTab/DataQualityTab.tsx | 74 +++++++++++++------ .../profilerDashboard.interface.ts | 5 ++ .../IncidentManagerDetailPage.tsx | 63 ++++++++++------ .../useTestCase.store.ts | 17 +++++ .../TestSuiteDetailsPage.component.tsx | 26 +++++-- 9 files changed, 167 insertions(+), 83 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/IncidentManagerPageHeader/IncidentManagerPageHeader.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/IncidentManagerPageHeader/IncidentManagerPageHeader.component.tsx index c417d45b4b62..4914f442b369 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/IncidentManagerPageHeader/IncidentManagerPageHeader.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/IncidentManagerPageHeader/IncidentManagerPageHeader.component.tsx @@ -19,8 +19,6 @@ import React, { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Link, useParams } from 'react-router-dom'; import { getEntityDetailsPath } from '../../../../constants/constants'; -import { usePermissionProvider } from '../../../../context/PermissionProvider/PermissionProvider'; -import { ResourceEntity } from '../../../../context/PermissionProvider/PermissionProvider.interface'; import { EntityTabs, EntityType } from '../../../../enums/entity.enum'; import { ThreadType } from '../../../../generated/api/feed/createThread'; import { CreateTestCaseResolutionStatus } from '../../../../generated/api/tests/createTestCaseResolutionStatus'; @@ -28,13 +26,13 @@ import { Thread, ThreadTaskStatus, } from '../../../../generated/entity/feed/thread'; -import { Operation } from '../../../../generated/entity/policies/policy'; import { EntityReference } from '../../../../generated/tests/testCase'; import { Severities, TestCaseResolutionStatus, TestCaseResolutionStatusTypes, } from '../../../../generated/tests/testCaseResolutionStatus'; +import { useTestCaseStore } from '../../../../pages/IncidentManager/IncidentManagerDetailPage/useTestCase.store'; import { getListTestCaseIncidentByStateId, postTestCaseIncidentStatus, @@ -46,7 +44,6 @@ import { getEntityName, } from '../../../../utils/EntityUtils'; import { getEntityFQN } from '../../../../utils/FeedUtils'; -import { checkPermission } from '../../../../utils/PermissionsUtils'; import { getDecodedFqn } from '../../../../utils/StringsUtils'; import { getTaskDetailPath } from '../../../../utils/TasksUtils'; import { showErrorToast } from '../../../../utils/ToastUtils'; @@ -59,7 +56,6 @@ import { IncidentManagerPageHeaderProps } from './IncidentManagerPageHeader.inte const IncidentManagerPageHeader = ({ onOwnerUpdate, - testCaseData, fetchTaskCount, }: IncidentManagerPageHeaderProps) => { const { t } = useTranslation(); @@ -67,6 +63,7 @@ const IncidentManagerPageHeader = ({ const [testCaseStatusData, setTestCaseStatusData] = useState(); const [isLoading, setIsLoading] = useState(true); + const { testCase: testCaseData, testCasePermission } = useTestCaseStore(); const { fqn } = useParams<{ fqn: string }>(); const decodedFqn = getDecodedFqn(fqn); @@ -212,14 +209,14 @@ const IncidentManagerPageHeader = ({ } }, [testCaseData]); - const { permissions } = usePermissionProvider(); - const hasEditPermission = useMemo(() => { - return checkPermission( - Operation.EditAll, - ResourceEntity.TEST_CASE, - permissions - ); - }, [permissions]); + const { hasEditStatusPermission, hasEditOwnerPermission } = useMemo(() => { + return { + hasEditStatusPermission: + testCasePermission?.EditAll || testCasePermission?.EditStatus, + hasEditOwnerPermission: + testCasePermission?.EditAll || testCasePermission?.EditOwners, + }; + }, []); const statusDetails = useMemo(() => { if (isLoading) { @@ -257,7 +254,7 @@ const IncidentManagerPageHeader = ({ className="font-medium" data-testid="table-name" to={getTaskDetailPath(activeTask)}> - {`#${activeTask?.task?.id}` ?? '--'} + {`#${activeTask?.task?.id}`} @@ -281,7 +278,7 @@ const IncidentManagerPageHeader = ({ {`${t('label.assignee')}: `} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/TestCaseResultTab/TestCaseResultTab.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/TestCaseResultTab/TestCaseResultTab.component.tsx index 379d942f481a..22172643f4cb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/TestCaseResultTab/TestCaseResultTab.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/TestCaseResultTab/TestCaseResultTab.component.tsx @@ -23,17 +23,13 @@ import { DE_ACTIVE_COLOR, ICON_DIMENSION, } from '../../../../constants/constants'; -import { usePermissionProvider } from '../../../../context/PermissionProvider/PermissionProvider'; -import { ResourceEntity } from '../../../../context/PermissionProvider/PermissionProvider.interface'; import { CSMode } from '../../../../enums/codemirror.enum'; import { EntityType } from '../../../../enums/entity.enum'; -import { Operation } from '../../../../generated/entity/policies/policy'; import { ReactComponent as StarIcon } from '../../../../assets/svg/ic-suggestions.svg'; import { TestCaseParameterValue } from '../../../../generated/tests/testCase'; import { useTestCaseStore } from '../../../../pages/IncidentManager/IncidentManagerDetailPage/useTestCase.store'; import { updateTestCaseById } from '../../../../rest/testAPI'; -import { checkPermission } from '../../../../utils/PermissionsUtils'; import { showErrorToast, showSuccessToast } from '../../../../utils/ToastUtils'; import DescriptionV1 from '../../../common/EntityDescription/DescriptionV1'; import TestSummary from '../../../Database/Profiler/TestSummary/TestSummary'; @@ -49,19 +45,20 @@ const TestCaseResultTab = () => { testCase: testCaseData, setTestCase, showAILearningBanner, + testCasePermission, } = useTestCaseStore(); const additionalComponent = testCaseResultTabClassBase.getAdditionalComponents(); const [isDescriptionEdit, setIsDescriptionEdit] = useState(false); const [isParameterEdit, setIsParameterEdit] = useState(false); - const { permissions } = usePermissionProvider(); - const hasEditPermission = useMemo(() => { - return checkPermission( - Operation.EditAll, - ResourceEntity.TEST_CASE, - permissions - ); - }, [permissions]); + + const { hasEditPermission, hasEditDescriptionPermission } = useMemo(() => { + return { + hasEditPermission: testCasePermission?.EditAll, + hasEditDescriptionPermission: + testCasePermission?.EditAll || testCasePermission?.EditDescription, + }; + }, [testCasePermission]); const { withSqlParams, withoutSqlParams } = useMemo(() => { const params = testCaseData?.parameterValues ?? []; @@ -168,7 +165,7 @@ const TestCaseResultTab = () => { setIsDescriptionEdit(false)} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/TestCaseStatus/TestCaseIncidentManagerStatus.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/TestCaseStatus/TestCaseIncidentManagerStatus.component.tsx index 11677985027d..62e129844bf5 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/TestCaseStatus/TestCaseIncidentManagerStatus.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/TestCaseStatus/TestCaseIncidentManagerStatus.component.tsx @@ -35,16 +35,20 @@ const TestCaseIncidentManagerStatus = ({ data, onSubmit, usersList, + hasPermission, }: TestCaseStatusIncidentManagerProps) => { const [isEditStatus, setIsEditStatus] = useState(false); const statusType = useMemo(() => data.testCaseResolutionStatusType, [data]); const { permissions } = usePermissionProvider(); const hasEditPermission = useMemo(() => { - return checkPermission( - Operation.EditAll, - ResourceEntity.TEST_CASE, - permissions + return ( + hasPermission ?? + checkPermission( + Operation.EditAll, + ResourceEntity.TEST_CASE_RESOLUTION_STATUS, + permissions + ) ); }, [permissions]); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/TestCaseStatus/TestCaseIncidentManagerStatus.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/TestCaseStatus/TestCaseIncidentManagerStatus.interface.ts index 8fc98be9fe24..415a5e81d15a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/TestCaseStatus/TestCaseIncidentManagerStatus.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/TestCaseStatus/TestCaseIncidentManagerStatus.interface.ts @@ -18,4 +18,5 @@ export interface TestCaseStatusIncidentManagerProps { data: TestCaseResolutionStatus; onSubmit: (data: TestCaseResolutionStatus) => void; usersList?: EntityReference[]; + hasPermission?: boolean; } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/DataQualityTab/DataQualityTab.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/DataQualityTab/DataQualityTab.tsx index 08d3dd98f4fe..16275a92547f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/DataQualityTab/DataQualityTab.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/DataQualityTab/DataQualityTab.tsx @@ -31,7 +31,6 @@ import { usePermissionProvider } from '../../../../context/PermissionProvider/Pe import { ResourceEntity } from '../../../../context/PermissionProvider/PermissionProvider.interface'; import { SORT_ORDER } from '../../../../enums/common.enum'; import { EntityTabs, EntityType } from '../../../../enums/entity.enum'; -import { Operation } from '../../../../generated/entity/policies/policy'; import { TestCase, TestCaseResult, @@ -50,7 +49,6 @@ import { getEntityName, } from '../../../../utils/EntityUtils'; import { getEntityFQN } from '../../../../utils/FeedUtils'; -import { checkPermission } from '../../../../utils/PermissionsUtils'; import { getIncidentManagerDetailPagePath } from '../../../../utils/RouterUtils'; import { replacePlus } from '../../../../utils/StringsUtils'; import { showErrorToast } from '../../../../utils/ToastUtils'; @@ -66,6 +64,7 @@ import { DataQualityTabProps, TableProfilerTab, TestCaseAction, + TestCasePermission, } from '../ProfilerDashboard/profilerDashboard.interface'; import './data-quality-tab.less'; @@ -82,30 +81,18 @@ const DataQualityTab: React.FC = ({ fetchTestCases, }) => { const { t } = useTranslation(); - const { permissions } = usePermissionProvider(); + const { getEntityPermissionByFqn } = usePermissionProvider(); const [selectedTestCase, setSelectedTestCase] = useState(); const [isStatusLoading, setIsStatusLoading] = useState(true); const [testCaseStatus, setTestCaseStatus] = useState< TestCaseResolutionStatus[] >([]); + const [isPermissionLoading, setIsPermissionLoading] = useState(true); + const [testCasePermissions, setTestCasePermissions] = useState< + TestCasePermission[] + >([]); const isApiSortingEnabled = useRef(false); - const testCaseEditPermission = useMemo(() => { - return checkPermission( - Operation.EditAll, - ResourceEntity.TEST_CASE, - permissions - ); - }, [permissions]); - - const testCaseDeletePermission = useMemo(() => { - return checkPermission( - Operation.Delete, - ResourceEntity.TEST_CASE, - permissions - ); - }, [permissions]); - const sortedData = useMemo( () => isApiSortingEnabled.current @@ -312,6 +299,18 @@ const DataQualityTab: React.FC = ({ width: 100, fixed: 'right', render: (_, record) => { + if (isPermissionLoading) { + return ; + } + + const testCasePermission = testCasePermissions.find( + (permission) => + permission.fullyQualifiedName === record.fullyQualifiedName + ); + + const testCaseEditPermission = testCasePermission?.EditAll; + const testCaseDeletePermission = testCasePermission?.Delete; + return ( = ({ return data; }, [ - testCaseEditPermission, - testCaseDeletePermission, testCases, testCaseStatus, isStatusLoading, + isPermissionLoading, + testCasePermissions, ]); const fetchTestCaseStatus = async () => { @@ -422,6 +421,38 @@ const DataQualityTab: React.FC = ({ } }; + const fetchTestCasePermissions = async () => { + try { + setIsPermissionLoading(true); + const promises = testCases.map((testCase) => { + return getEntityPermissionByFqn( + ResourceEntity.TEST_CASE, + testCase.fullyQualifiedName ?? '' + ); + }); + const testCasePermission = await Promise.allSettled(promises); + const data = testCasePermission.reduce((acc, status, i) => { + if (status.status === 'fulfilled') { + return [ + ...acc, + { + ...status.value, + fullyQualifiedName: testCases[i].fullyQualifiedName, + }, + ]; + } + + return acc; + }, [] as TestCasePermission[]); + + setTestCasePermissions(data); + } catch (error) { + // do nothing + } finally { + setIsPermissionLoading(false); + } + }; + const handleTableChange = ( _pagination: TablePaginationConfig, _filters: Record, @@ -448,6 +479,7 @@ const DataQualityTab: React.FC = ({ useEffect(() => { if (testCases.length) { fetchTestCaseStatus(); + fetchTestCasePermissions(); } else { setIsStatusLoading(false); } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/ProfilerDashboard/profilerDashboard.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/ProfilerDashboard/profilerDashboard.interface.ts index 2c249b7e3a73..171dde89ed0a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/ProfilerDashboard/profilerDashboard.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/Profiler/ProfilerDashboard/profilerDashboard.interface.ts @@ -12,6 +12,7 @@ */ import { CurveType } from 'recharts/types/shape/Curve'; +import { OperationPermission } from '../../../../context/PermissionProvider/PermissionProvider.interface'; import { Thread } from '../../../../generated/entity/feed/thread'; import { TestCase } from '../../../../generated/tests/testCase'; import { TestSuite } from '../../../../generated/tests/testSuite'; @@ -87,3 +88,7 @@ export type TestCaseChartDataType = { export interface LineChartRef { container: HTMLElement; } + +export type TestCasePermission = OperationPermission & { + fullyQualifiedName?: string; +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/IncidentManager/IncidentManagerDetailPage/IncidentManagerDetailPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/IncidentManager/IncidentManagerDetailPage/IncidentManagerDetailPage.tsx index 2e535783fbf8..57bbda22a1cf 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/IncidentManager/IncidentManagerDetailPage/IncidentManagerDetailPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/IncidentManager/IncidentManagerDetailPage/IncidentManagerDetailPage.tsx @@ -34,13 +34,11 @@ import { usePermissionProvider } from '../../../context/PermissionProvider/Permi import { ResourceEntity } from '../../../context/PermissionProvider/PermissionProvider.interface'; import { ERROR_PLACEHOLDER_TYPE } from '../../../enums/common.enum'; import { EntityTabs, EntityType } from '../../../enums/entity.enum'; -import { Operation } from '../../../generated/entity/policies/policy'; import { EntityReference } from '../../../generated/tests/testCase'; import { useFqn } from '../../../hooks/useFqn'; import { FeedCounts } from '../../../interface/feed.interface'; import { getTestCaseByFqn, updateTestCaseById } from '../../../rest/testAPI'; import { getFeedCounts } from '../../../utils/CommonUtils'; -import { checkPermission } from '../../../utils/PermissionsUtils'; import { getIncidentManagerDetailPagePath } from '../../../utils/RouterUtils'; import { showErrorToast } from '../../../utils/ToastUtils'; import { IncidentManagerTabs } from '../IncidentManager.interface'; @@ -58,33 +56,32 @@ const IncidentManagerDetailPage = () => { const { fqn: testCaseFQN } = useFqn(); - const { isLoading, setIsLoading, setTestCase, testCase, reset } = - useTestCaseStore(); + const { + isLoading, + setIsLoading, + setTestCase, + testCase, + reset, + isPermissionLoading, + testCasePermission, + setTestCasePermission, + setIsPermissionLoading, + } = useTestCaseStore(); const [feedCount, setFeedCount] = useState( FEED_COUNT_INITIAL_DATA ); - const { permissions } = usePermissionProvider(); + const { getEntityPermissionByFqn } = usePermissionProvider(); const { hasViewPermission, editDisplayNamePermission, hasDeletePermission } = useMemo(() => { return { - hasViewPermission: checkPermission( - Operation.ViewAll, - ResourceEntity.TEST_CASE, - permissions - ), - editDisplayNamePermission: checkPermission( - Operation.EditDisplayName, - ResourceEntity.TEST_CASE, - permissions - ), - hasDeletePermission: checkPermission( - Operation.Delete, - ResourceEntity.TEST_CASE, - permissions - ), + hasViewPermission: + testCasePermission?.ViewAll || testCasePermission?.ViewBasic, + editDisplayNamePermission: + testCasePermission?.EditAll || testCasePermission?.EditDisplayName, + hasDeletePermission: testCasePermission?.Delete, }; - }, [permissions]); + }, [testCasePermission]); const tabDetails: TabsProps['items'] = useMemo(() => { const tabs = testCaseClassBase.getTab(feedCount.openTaskCount); @@ -96,6 +93,22 @@ const IncidentManagerDetailPage = () => { })); }, [feedCount.openTaskCount, testCaseClassBase.showSqlQueryTab]); + const fetchTestCasePermission = async () => { + setIsPermissionLoading(true); + try { + const response = await getEntityPermissionByFqn( + ResourceEntity.TEST_CASE, + testCaseFQN + ); + + setTestCasePermission(response); + } catch (error) { + showErrorToast(error as AxiosError); + } finally { + setIsPermissionLoading(false); + } + }; + const fetchTestCaseData = async () => { setIsLoading(true); try { @@ -195,6 +208,12 @@ const IncidentManagerDetailPage = () => { getFeedCounts(EntityType.TEST_CASE, testCaseFQN, handleFeedCount); }, [testCaseFQN]); + useEffect(() => { + if (testCaseFQN) { + fetchTestCasePermission(); + } + }, [testCaseFQN]); + useEffect(() => { if (hasViewPermission && testCaseFQN) { fetchTestCaseData(); @@ -210,7 +229,7 @@ const IncidentManagerDetailPage = () => { }; }, [testCaseFQN, hasViewPermission]); - if (isLoading) { + if (isLoading || isPermissionLoading) { return ; } diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/IncidentManager/IncidentManagerDetailPage/useTestCase.store.ts b/openmetadata-ui/src/main/resources/ui/src/pages/IncidentManager/IncidentManagerDetailPage/useTestCase.store.ts index 6e5f55619626..ed2d37649687 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/IncidentManager/IncidentManagerDetailPage/useTestCase.store.ts +++ b/openmetadata-ui/src/main/resources/ui/src/pages/IncidentManager/IncidentManagerDetailPage/useTestCase.store.ts @@ -12,12 +12,19 @@ */ import { create } from 'zustand'; import { EntityLineageResponse } from '../../../components/Lineage/Lineage.interface'; +import { OperationPermission } from '../../../context/PermissionProvider/PermissionProvider.interface'; import { TestCase } from '../../../generated/tests/testCase'; export interface UseTestCaseStoreInterface { testCase: TestCase | undefined; isLoading: boolean; + isPermissionLoading: boolean; showAILearningBanner: boolean; + testCasePermission: OperationPermission | undefined; + setTestCasePermission: ( + testCasePermission: OperationPermission | undefined + ) => void; + setIsPermissionLoading: (isPermissionLoading: boolean) => void; setTestCase: (testCase: TestCase) => void; setIsLoading: (isLoading: boolean) => void; setShowAILearningBanner: (showBanner: boolean) => void; @@ -29,10 +36,20 @@ export const useTestCaseStore = create()((set) => ({ testCase: undefined, dqLineageData: undefined, isLoading: true, + isPermissionLoading: true, showAILearningBanner: false, + testCasePermission: undefined, setTestCase: (testCase: TestCase) => { set({ testCase }); }, + setTestCasePermission: ( + testCasePermission: OperationPermission | undefined + ) => { + set({ testCasePermission }); + }, + setIsPermissionLoading: (isPermissionLoading: boolean) => { + set({ isPermissionLoading }); + }, setIsLoading: (isLoading: boolean) => { set({ isLoading }); }, diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TestSuiteDetailsPage/TestSuiteDetailsPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TestSuiteDetailsPage/TestSuiteDetailsPage.component.tsx index 2c1cbec1e395..5a77fb38af99 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/TestSuiteDetailsPage/TestSuiteDetailsPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/TestSuiteDetailsPage/TestSuiteDetailsPage.component.tsx @@ -43,7 +43,6 @@ import { EntityType, TabSpecificField } from '../../enums/entity.enum'; import { TestCase } from '../../generated/tests/testCase'; import { TestSuite } from '../../generated/tests/testSuite'; import { Include } from '../../generated/type/include'; -import { useAuth } from '../../hooks/authHooks'; import { usePaging } from '../../hooks/paging/usePaging'; import { useFqn } from '../../hooks/useFqn'; import { DataQualityPageTabs } from '../../pages/DataQuality/DataQualityPage.interface'; @@ -67,7 +66,6 @@ const TestSuiteDetailsPage = () => { const { t } = useTranslation(); const { getEntityPermissionByFqn } = usePermissionProvider(); const { fqn: testSuiteFQN } = useFqn(); - const { isAdminUser } = useAuth(); const history = useHistory(); const afterDeleteAction = () => { @@ -107,6 +105,20 @@ const TestSuiteDetailsPage = () => { }; }, [testSuite]); + const permissions = useMemo(() => { + return { + hasViewPermission: + testSuitePermissions?.ViewAll || testSuitePermissions?.ViewBasic, + hasEditPermission: testSuitePermissions?.EditAll, + hasEditOwnerPermission: + testSuitePermissions?.EditAll || testSuitePermissions?.EditOwners, + hasEditDescriptionPermission: + testSuitePermissions?.EditAll || testSuitePermissions?.EditDescription, + hasDeletePermission: + testSuitePermissions?.EditAll || testSuitePermissions?.Delete, + }; + }, [testSuitePermissions]); + const incidentUrlState = useMemo(() => { return [ { @@ -302,10 +314,10 @@ const TestSuiteDetailsPage = () => { }; useEffect(() => { - if (testSuitePermissions.ViewAll || testSuitePermissions.ViewBasic) { + if (permissions.hasViewPermission) { fetchTestSuiteByName(); } - }, [testSuitePermissions, testSuiteFQN]); + }, [permissions, testSuiteFQN]); useEffect(() => { fetchTestSuitePermission(); @@ -365,7 +377,7 @@ const TestSuiteDetailsPage = () => { isRecursiveDelete afterDeleteAction={afterDeleteAction} allowSoftDelete={false} - canDelete={isAdminUser} + canDelete={permissions.hasDeletePermission} deleted={testSuite?.deleted} displayName={getEntityName(testSuite)} entityId={testSuite?.id} @@ -377,7 +389,7 @@ const TestSuiteDetailsPage = () => {
@@ -388,7 +400,7 @@ const TestSuiteDetailsPage = () => { description={testSuiteDescription} entityName={getEntityName(testSuite)} entityType={EntityType.TEST_SUITE} - hasEditAccess={isAdminUser} + hasEditAccess={permissions.hasEditDescriptionPermission} isEdit={isDescriptionEditable} showCommentsIcon={false} onCancel={() => descriptionHandler(false)}