fix(ui): added no permission placeholder instead 404 (#11205)

* fix(ui): added no permission placeholder instead 404

* fix unit tests
This commit is contained in:
Chirag Madlani 2023-04-23 20:03:23 +05:30 committed by GitHub
parent e984d274d8
commit f53ea03320
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 322 additions and 299 deletions

View File

@ -16,6 +16,7 @@ import { AxiosError } from 'axios';
import classNames from 'classnames';
import SummaryTagsDescription from 'components/common/SummaryTagsDescription/SummaryTagsDescription.component';
import SummaryPanelSkeleton from 'components/Skeleton/SummaryPanelSkeleton/SummaryPanelSkeleton.component';
import { ClientErrors } from 'enums/axios.enum';
import { ExplorePageTabs } from 'enums/Explore.enum';
import { isEmpty, isUndefined } from 'lodash';
import {
@ -120,7 +121,9 @@ function TableSummary({
return {} as Table;
}
});
} catch {
} catch (error) {
const axiosError = error as AxiosError;
if (axiosError.response?.status !== ClientErrors.FORBIDDEN) {
showErrorToast(
t('server.entity-details-fetch-error', {
entityType: t('label.table-lowercase'),
@ -128,6 +131,7 @@ function TableSummary({
})
);
}
}
}, [entityDetails]);
const overallSummary: OverallTableSummeryType[] | undefined = useMemo(() => {

View File

@ -12,9 +12,11 @@
*/
import { Col, Divider, Row, Typography } from 'antd';
import { AxiosError } from 'axios';
import SummaryTagsDescription from 'components/common/SummaryTagsDescription/SummaryTagsDescription.component';
import SummaryPanelSkeleton from 'components/Skeleton/SummaryPanelSkeleton/SummaryPanelSkeleton.component';
import { getTeamAndUserDetailsPath } from 'constants/constants';
import { ClientErrors } from 'enums/axios.enum';
import { isArray, isEmpty } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
@ -90,7 +92,9 @@ function TopicSummary({
const { partitions, messageSchema } = res;
setTopicDetails({ ...entityDetails, partitions, messageSchema });
} catch {
} catch (error) {
const axiosError = error as AxiosError;
if (axiosError.response?.status !== ClientErrors.FORBIDDEN) {
showErrorToast(
t('server.entity-details-fetch-error', {
entityType: t('label.topic-lowercase'),
@ -98,6 +102,7 @@ function TopicSummary({
})
);
}
}
}, [entityDetails]);
const formattedSchemaFieldsData: BasicEntityInfo[] = useMemo(

View File

@ -27,31 +27,55 @@ jest.mock('../SummaryList/SummaryList.component', () =>
);
jest.mock('rest/topicsAPI', () => ({
getTopicByFqn: jest.fn().mockImplementation(() => mockTopicByFqnResponse),
getTopicByFqn: jest
.fn()
.mockImplementation(() => Promise.resolve(mockTopicByFqnResponse)),
}));
jest.mock(
'components/common/SummaryTagsDescription/SummaryTagsDescription.component',
() => jest.fn().mockImplementation(() => <p>SummaryTagDescription</p>)
);
jest.mock(
'components/Skeleton/SummaryPanelSkeleton/SummaryPanelSkeleton.component',
() => jest.fn().mockImplementation(({ children }) => <>{children}</>)
);
describe('TopicSummary component tests', () => {
it('Component should render properly', async () => {
await act(async () => {
render(<TopicSummary entityDetails={mockTopicEntityDetails} />);
});
const partitionsLabel = screen.getByTestId('Partitions-label');
const replicationFactorLabel = screen.getByTestId(
const partitionsLabel = await screen.findByTestId('Partitions-label');
const replicationFactorLabel = await screen.findByTestId(
'Replication Factor-label'
);
const retentionSizeLabel = screen.getByTestId('Retention Size-label');
const cleanUpPoliciesLabel = screen.getByTestId('CleanUp Policies-label');
const maxMessageSizeLabel = screen.getByTestId('Max Message Size-label');
const partitionsValue = screen.getByTestId('Partitions-value');
const replicationFactorValue = screen.getByTestId(
const retentionSizeLabel = await screen.findByTestId(
'Retention Size-label'
);
const cleanUpPoliciesLabel = await screen.findByTestId(
'CleanUp Policies-label'
);
const maxMessageSizeLabel = await screen.findByTestId(
'Max Message Size-label'
);
const partitionsValue = await screen.findByTestId('Partitions-value');
const replicationFactorValue = await screen.findByTestId(
'Replication Factor-value'
);
const retentionSizeValue = screen.getByTestId('Retention Size-value');
const cleanUpPoliciesValue = screen.getByTestId('CleanUp Policies-value');
const maxMessageSizeValue = screen.getByTestId('Max Message Size-value');
const schemaHeader = screen.getByTestId('schema-header');
const summaryList = screen.getByTestId('SummaryList');
const retentionSizeValue = await screen.findByTestId(
'Retention Size-value'
);
const cleanUpPoliciesValue = await screen.findByTestId(
'CleanUp Policies-value'
);
const maxMessageSizeValue = await screen.findByTestId(
'Max Message Size-value'
);
const schemaHeader = await screen.findByTestId('schema-header');
const summaryList = await screen.findByTestId('SummaryList');
expect(partitionsLabel).toBeInTheDocument();
expect(replicationFactorLabel).toBeInTheDocument();
@ -68,7 +92,9 @@ describe('TopicSummary component tests', () => {
});
it('No data message should be shown in case no schemaFields are available in topic details', async () => {
(getTopicByFqn as jest.Mock).mockImplementation(() => Promise.resolve({}));
(getTopicByFqn as jest.Mock).mockImplementation(() =>
Promise.resolve({ ...mockTopicEntityDetails, messageSchema: {} })
);
await act(async () => {
render(<TopicSummary entityDetails={mockTopicEntityDetails} />);
@ -82,7 +108,9 @@ describe('TopicSummary component tests', () => {
});
it('In case any topic field is not present, "-" should be displayed in place of value', async () => {
(getTopicByFqn as jest.Mock).mockImplementationOnce(() => Promise.reject());
(getTopicByFqn as jest.Mock).mockImplementationOnce(() =>
Promise.reject({})
);
await act(async () => {
render(<TopicSummary entityDetails={mockTopicEntityDetails} />);
});

View File

@ -1,35 +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 React from 'react';
import { useTranslation } from 'react-i18next';
import PageLayoutV1 from '../containers/PageLayoutV1';
import GlobalSettingRouter from '../router/GlobalSettingRouter';
import './GlobalSetting.less';
import GlobalSettingLeftPanel from './GlobalSettingLeftPanel';
const GlobalSetting = () => {
const { t } = useTranslation();
return (
<PageLayoutV1
className="tw-h-full tw-px-6"
leftPanel={<GlobalSettingLeftPanel />}
pageTitle={t('label.setting-plural')}>
<GlobalSettingRouter />
</PageLayoutV1>
);
};
export default GlobalSetting;

View File

@ -1,81 +0,0 @@
/*
* Copyright 2023 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 { render, screen, waitForElement } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { PERMISSIONS } from 'mocks/Permissions.mock';
import React from 'react';
import { MemoryRouter, Route } from 'react-router-dom';
import GlobalSettingLeftPanel from './GlobalSettingLeftPanel';
jest.mock('../PermissionProvider/PermissionProvider', () => ({
usePermissionProvider: jest.fn().mockImplementation(() => ({
permissions: PERMISSIONS,
getEntityPermission: jest.fn().mockResolvedValue({
Create: true,
Delete: true,
EditAll: true,
EditCustomFields: true,
EditDataProfile: true,
EditDescription: true,
EditDisplayName: true,
EditLineage: true,
EditOwner: true,
EditQueries: true,
EditSampleData: true,
EditTags: true,
EditTests: true,
EditTier: true,
ViewAll: true,
ViewDataProfile: true,
ViewQueries: true,
ViewSampleData: true,
ViewTests: true,
ViewUsage: true,
}),
})),
}));
const selectedCategory = 'services';
const selectedOption = 'storages';
const url = `/settings/${selectedCategory}/${selectedOption}`;
describe('GlobalSettingLeftPanel', () => {
it('Should render global settings menu with correct selected item', () => {
render(
<MemoryRouter initialEntries={[url]}>
<Route path="/settings/:settingCategory/:tab">
<GlobalSettingLeftPanel />
</Route>
</MemoryRouter>
);
expect(screen.getByText('label.service-plural')).toBeInTheDocument();
expect(screen.getByText('label.storage-plural')).toBeInTheDocument();
});
it('Should change the location path on user click', () => {
render(
<MemoryRouter initialEntries={[url]}>
<Route path="/settings/:settingCategory/:tab">
<GlobalSettingLeftPanel />
</Route>
</MemoryRouter>
);
userEvent.click(screen.getByText('label.team-plural'));
waitForElement(() =>
expect(window.location.pathname).toEqual('/teams/organizations')
);
});
});

View File

@ -1,113 +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 { Menu, MenuProps } from 'antd';
import { ItemType } from 'antd/lib/menu/hooks/useItems';
import { ELASTIC_SEARCH_RE_INDEX_PAGE_TABS } from 'enums/ElasticSearch.enum';
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory, useParams } from 'react-router-dom';
import { GlobalSettingOptions } from '../../constants/GlobalSettings.constants';
import { TeamType } from '../../generated/entity/teams/team';
import { useAuth } from '../../hooks/authHooks';
import {
getGlobalSettingMenuItem,
getGlobalSettingsMenuWithPermission,
MenuList,
} from '../../utils/GlobalSettingsUtils';
import {
getSettingPath,
getSettingsPathWithFqn,
getTeamsWithFqnPath,
} from '../../utils/RouterUtils';
import ErrorPlaceHolder from '../common/error-with-placeholder/ErrorPlaceHolder';
import LeftPanelCard from '../common/LeftPanelCard/LeftPanelCard';
import { usePermissionProvider } from '../PermissionProvider/PermissionProvider';
const GlobalSettingLeftPanel = () => {
const history = useHistory();
const { t } = useTranslation();
const { tab, settingCategory } = useParams<{ [key: string]: string }>();
const { permissions } = usePermissionProvider();
const { isAdminUser } = useAuth();
const menuItems: ItemType[] = useMemo(
() =>
getGlobalSettingsMenuWithPermission(permissions, isAdminUser).reduce(
(acc: ItemType[], curr: MenuList) => {
const menuItem = getGlobalSettingMenuItem({
label: curr.category,
key: curr.key,
category: curr.category,
children: curr.items,
type: 'group',
isBeta: curr.isBeta,
});
if (menuItem.children?.length) {
return [...acc, menuItem];
} else {
return acc;
}
},
[] as ItemType[]
),
[permissions]
);
const onClick: MenuProps['onClick'] = (e) => {
// As we are setting key as "category.option" and extracting here category and option
const [category, option] = e.key.split('.');
switch (option) {
case GlobalSettingOptions.TEAMS:
history.push(getTeamsWithFqnPath(TeamType.Organization));
break;
case GlobalSettingOptions.SEARCH:
history.push(
getSettingsPathWithFqn(
category,
option,
ELASTIC_SEARCH_RE_INDEX_PAGE_TABS.ON_DEMAND
)
);
break;
default:
history.push(getSettingPath(category, option));
break;
}
};
return menuItems.length ? (
<LeftPanelCard id="settings">
<Menu
className="custom-menu"
data-testid="global-setting-left-panel"
items={menuItems}
mode="inline"
selectedKeys={[`${settingCategory}.${tab}`]}
onClick={onClick}
/>
</LeftPanelCard>
) : (
<ErrorPlaceHolder>
<p>{t('message.no-data-available')}</p>
</ErrorPlaceHolder>
);
};
export default GlobalSettingLeftPanel;

View File

@ -11,11 +11,15 @@
* limitations under the License.
*/
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
import {
NO_PERMISSION_TO_VIEW,
REACH_OUT_TO_ADMIN_FOR_ACCESS,
} from 'constants/HelperTextUtil';
import React from 'react';
import { Redirect, Route, RouteProps } from 'react-router-dom';
import { ROUTES } from '../../constants/constants';
import { useAuth } from '../../hooks/authHooks';
import { useAuthContext } from '../authentication/auth-provider/AuthProvider';
interface AdminProtectedRouteProps extends RouteProps {
hasPermission: boolean;
@ -23,12 +27,17 @@ interface AdminProtectedRouteProps extends RouteProps {
const AdminProtectedRoute = (routeProps: AdminProtectedRouteProps) => {
const { isAdminUser } = useAuth();
const { isAuthDisabled, isAuthenticated } = useAuthContext();
if (isAuthDisabled || isAdminUser || routeProps.hasPermission) {
if (isAdminUser || routeProps.hasPermission) {
return <Route {...routeProps} />;
} else if (isAuthenticated) {
return <Redirect to={ROUTES.NOT_FOUND} />;
} else if (!routeProps.hasPermission) {
return (
<ErrorPlaceHolder>
<p className="text-center">
{NO_PERMISSION_TO_VIEW} <br /> {REACH_OUT_TO_ADMIN_FOR_ACCESS}
</p>
</ErrorPlaceHolder>
);
} else {
return <Redirect to={ROUTES.SIGNIN} />;
}

View File

@ -245,6 +245,10 @@ const AddQueryPage = withSuspenseFallback(
React.lazy(() => import('pages/AddQueryPage/AddQueryPage.component'))
);
const PageNotFound = withSuspenseFallback(
React.lazy(() => import('pages/page-not-found'))
);
const AuthenticatedAppRouter: FunctionComponent = () => {
const { permissions } = usePermissionProvider();
@ -603,6 +607,7 @@ const AuthenticatedAppRouter: FunctionComponent = () => {
<Route exact path={ROUTES.HOME}>
<Redirect to={ROUTES.MY_DATA} />
</Route>
<Route exact component={PageNotFound} path={ROUTES.NOT_FOUND} />
<Redirect to={ROUTES.NOT_FOUND} />
</Switch>
);

View File

@ -17,7 +17,6 @@ import {
GlobalSettingOptions,
GlobalSettingsMenuCategory,
} from '../../constants/GlobalSettings.constants';
import { TeamType } from '../../generated/entity/teams/team';
import { userPermissions } from '../../utils/PermissionsUtils';
import {
@ -27,7 +26,6 @@ import {
} from '../../utils/RouterUtils';
import { usePermissionProvider } from '../PermissionProvider/PermissionProvider';
import { ResourceEntity } from '../PermissionProvider/PermissionProvider.interface';
import AdminProtectedRoute from './AdminProtectedRoute';
import withSuspenseFallback from './withSuspenseFallback';
@ -117,9 +115,13 @@ const GlobalSettingRouter = () => {
<Route exact path={getSettingPath()}>
<Redirect to={getTeamsWithFqnPath(TeamType.Organization)} />
</Route>
<Route
<AdminProtectedRoute
exact
component={TeamsPage}
hasPermission={userPermissions.hasViewPermissions(
ResourceEntity.TEAM,
permissions
)}
path={getSettingPath(
GlobalSettingsMenuCategory.MEMBERS,
GlobalSettingOptions.TEAMS,

View File

@ -17,4 +17,8 @@ export const NO_PERMISSION_FOR_ACTION = t('message.no-permission-for-action');
export const NO_PERMISSION_TO_VIEW = t('message.no-permission-to-view');
export const REACH_OUT_TO_ADMIN_FOR_ACCESS = t(
'message.reach-out-to-admin-for-access'
);
export const ADMIN_ONLY_ACTION = t('message.admin-only-action');

View File

@ -1208,6 +1208,7 @@
"queries-result-test": "Queries returning 1 or more rows will result in the test failing.",
"query-log-duration-message": "Configuration to tune how far we want to look back in query logs to process usage data.",
"query-used-by-other-tables": "Query used by other tables",
"reach-out-to-admin-for-access": "Please reach out to your Admin to get access.",
"reacted-with-emoji": "reacted with {{type}} emoji",
"redirect-message": "Please wait while you are being redirected.",
"redirecting-to-home-page": "Redirecting to the home page",

View File

@ -1208,6 +1208,7 @@
"queries-result-test": "Las consultas que devuelvan 1 o más filas darán como resultado un fallo en el test.",
"query-log-duration-message": "Configuración para ajustar cuánto tiempo queremos retroceder en los registros de consultas para procesar datos de uso.",
"query-used-by-other-tables": "Consulta utilizada por otras tablas",
"reach-out-to-admin-for-access": "Please reach out to your Admin to get access.",
"reacted-with-emoji": "reaccionó con el emoji {{type}}",
"redirect-message": "Por favor espera mientras eres redirigido.",
"redirecting-to-home-page": "Redirigiendo a la página principal",

View File

@ -1208,6 +1208,7 @@
"queries-result-test": "Queries returning 1 or more rows will result in the test failing.",
"query-log-duration-message": "Configuration to tune how far we want to look back in query logs to process usage data.",
"query-used-by-other-tables": "Query used by other tables",
"reach-out-to-admin-for-access": "Please reach out to your Admin to get access.",
"reacted-with-emoji": "reacted with {{type}} emoji",
"redirect-message": "Please wait while you are being redirected.",
"redirecting-to-home-page": "Redirecting to the home page",

View File

@ -1208,6 +1208,7 @@
"queries-result-test": "Queries returning 1 or more rows will result in the test failing.",
"query-log-duration-message": "Configuration to tune how far we want to look back in query logs to process usage data.",
"query-used-by-other-tables": "Query used by other tables",
"reach-out-to-admin-for-access": "Please reach out to your Admin to get access.",
"reacted-with-emoji": "reacted with {{type}} emoji",
"redirect-message": "Please wait while you are being redirected.",
"redirecting-to-home-page": "ホームに戻っています",

View File

@ -1208,6 +1208,7 @@
"queries-result-test": "Consultas que retornam 1 ou mais linhas resultarão na falha do teste.",
"query-log-duration-message": "Configuração para ajustar até onde queremos olhar nos logs de consulta para processar os dados de uso.",
"query-used-by-other-tables": "Consulta usada por outras tabelas",
"reach-out-to-admin-for-access": "Please reach out to your Admin to get access.",
"reacted-with-emoji": "reagiu com um emoji {{type}}",
"redirect-message": "Please wait while you are being redirected.",
"redirecting-to-home-page": "Redirecionando para a página inicial",

View File

@ -1208,6 +1208,7 @@
"queries-result-test": "Queries returning 1 or more rows will result in the test failing.",
"query-log-duration-message": "Configuration to tune how far we want to look back in query logs to process usage data.",
"query-used-by-other-tables": "Query used by other tables",
"reach-out-to-admin-for-access": "Please reach out to your Admin to get access.",
"reacted-with-emoji": "reacted with {{type}} emoji",
"redirect-message": "Please wait while you are being redirected.",
"redirecting-to-home-page": "Redirecting to the home page",

View File

@ -33,7 +33,10 @@ import {
updateBotDetail,
updateUserDetail,
} from 'rest/userAPI';
import { NO_PERMISSION_TO_VIEW } from '../../constants/HelperTextUtil';
import {
NO_PERMISSION_TO_VIEW,
REACH_OUT_TO_ADMIN_FOR_ACCESS,
} from '../../constants/HelperTextUtil';
import { Bot } from '../../generated/entity/bot';
import { User } from '../../generated/entity/teams/user';
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
@ -181,7 +184,11 @@ const BotDetailsPage = () => {
{botPermission.ViewAll || botPermission.ViewBasic ? (
getBotsDetailComponent()
) : (
<ErrorPlaceHolder>{NO_PERMISSION_TO_VIEW}</ErrorPlaceHolder>
<ErrorPlaceHolder>
<p className="text-center">
{NO_PERMISSION_TO_VIEW} <br /> {REACH_OUT_TO_ADMIN_FOR_ACCESS}
</p>
</ErrorPlaceHolder>
)}
</>
)}

View File

@ -37,7 +37,10 @@ import {
} from 'components/PermissionProvider/PermissionProvider.interface';
import { FQN_SEPARATOR_CHAR } from 'constants/char.constants';
import { getServiceDetailsPath, getVersionPath } from 'constants/constants';
import { NO_PERMISSION_TO_VIEW } from 'constants/HelperTextUtil';
import {
NO_PERMISSION_TO_VIEW,
REACH_OUT_TO_ADMIN_FOR_ACCESS,
} from 'constants/HelperTextUtil';
import { EntityInfo, EntityType } from 'enums/entity.enum';
import { ServiceCategory } from 'enums/service.enum';
import { OwnerType } from 'enums/user.enum';
@ -620,7 +623,13 @@ const ContainerPage = () => {
}
if (!hasViewPermission && !isLoading) {
return <ErrorPlaceHolder>{NO_PERMISSION_TO_VIEW}</ErrorPlaceHolder>;
return (
<ErrorPlaceHolder>
<p className="text-center">
{NO_PERMISSION_TO_VIEW} <br /> {REACH_OUT_TO_ADMIN_FOR_ACCESS}
</p>
</ErrorPlaceHolder>
);
}
return (

View File

@ -39,6 +39,7 @@ import { CUSTOM_PROPERTIES_DOCS } from '../../constants/docs.constants';
import {
NO_PERMISSION_FOR_ACTION,
NO_PERMISSION_TO_VIEW,
REACH_OUT_TO_ADMIN_FOR_ACCESS,
} from '../../constants/HelperTextUtil';
import { PAGE_HEADERS } from '../../constants/PageHeaders.constant';
import { Type } from '../../generated/entity/type';
@ -297,7 +298,9 @@ const CustomEntityDetailV1 = () => {
<Row>
<Col span={24}>
<ErrorPlaceHolder>
<p>{NO_PERMISSION_TO_VIEW}</p>
<p className="text-center">
{NO_PERMISSION_TO_VIEW} <br /> {REACH_OUT_TO_ADMIN_FOR_ACCESS}
</p>
</ErrorPlaceHolder>
</Col>
</Row>

View File

@ -37,7 +37,10 @@ import {
getServiceDetailsPath,
getVersionPath,
} from '../../constants/constants';
import { NO_PERMISSION_TO_VIEW } from '../../constants/HelperTextUtil';
import {
NO_PERMISSION_TO_VIEW,
REACH_OUT_TO_ADMIN_FOR_ACCESS,
} from '../../constants/HelperTextUtil';
import { EntityType, TabSpecificField } from '../../enums/entity.enum';
import { FeedFilter } from '../../enums/mydata.enum';
import { ServiceCategory } from '../../enums/service.enum';
@ -540,7 +543,11 @@ const DashboardDetailsPage = () => {
onExtensionUpdate={handleExtensionUpdate}
/>
) : (
<ErrorPlaceHolder>{NO_PERMISSION_TO_VIEW}</ErrorPlaceHolder>
<ErrorPlaceHolder>
<p className="text-center">
{NO_PERMISSION_TO_VIEW} <br /> {REACH_OUT_TO_ADMIN_FOR_ACCESS}
</p>
</ErrorPlaceHolder>
)}
</>
)}

View File

@ -21,7 +21,10 @@ import {
OperationPermission,
ResourceEntity,
} from 'components/PermissionProvider/PermissionProvider.interface';
import { NO_PERMISSION_TO_VIEW } from 'constants/HelperTextUtil';
import {
NO_PERMISSION_TO_VIEW,
REACH_OUT_TO_ADMIN_FOR_ACCESS,
} from 'constants/HelperTextUtil';
import { EntityType } from 'enums/entity.enum';
import { FeedFilter } from 'enums/mydata.enum';
import { compare, Operation } from 'fast-json-patch';
@ -469,7 +472,13 @@ const DataModelsPage = () => {
}
if (!hasViewPermission && !isLoading) {
return <ErrorPlaceHolder>{NO_PERMISSION_TO_VIEW}</ErrorPlaceHolder>;
return (
<ErrorPlaceHolder>
<p className="text-center">
{NO_PERMISSION_TO_VIEW} <br /> {REACH_OUT_TO_ADMIN_FOR_ACCESS}
</p>
</ErrorPlaceHolder>
);
}
return (

View File

@ -50,7 +50,10 @@ import {
getVersionPath,
pagingObject,
} from '../../constants/constants';
import { NO_PERMISSION_TO_VIEW } from '../../constants/HelperTextUtil';
import {
NO_PERMISSION_TO_VIEW,
REACH_OUT_TO_ADMIN_FOR_ACCESS,
} from '../../constants/HelperTextUtil';
import { EntityType, FqnPart, TabSpecificField } from '../../enums/entity.enum';
import { FeedFilter } from '../../enums/mydata.enum';
import { ServiceCategory } from '../../enums/service.enum';
@ -620,7 +623,11 @@ const DatasetDetailsPage: FunctionComponent = () => {
versionHandler={versionHandler}
/>
) : (
<ErrorPlaceHolder>{NO_PERMISSION_TO_VIEW}</ErrorPlaceHolder>
<ErrorPlaceHolder>
<p className="text-center">
{NO_PERMISSION_TO_VIEW} <br /> {REACH_OUT_TO_ADMIN_FOR_ACCESS}
</p>
</ErrorPlaceHolder>
)}
</>
)}

View File

@ -11,14 +11,111 @@
* limitations under the License.
*/
import { Menu, MenuProps } from 'antd';
import { ItemType } from 'antd/lib/menu/hooks/useItems';
import LeftPanelCard from 'components/common/LeftPanelCard/LeftPanelCard';
import PageContainerV1 from 'components/containers/PageContainerV1';
import GlobalSetting from 'components/GlobalSetting/GlobalSetting';
import React from 'react';
import PageLayoutV1 from 'components/containers/PageLayoutV1';
import { usePermissionProvider } from 'components/PermissionProvider/PermissionProvider';
import GlobalSettingRouter from 'components/router/GlobalSettingRouter';
import { GlobalSettingOptions } from 'constants/GlobalSettings.constants';
import { ELASTIC_SEARCH_RE_INDEX_PAGE_TABS } from 'enums/ElasticSearch.enum';
import { TeamType } from 'generated/entity/teams/team';
import { useAuth } from 'hooks/authHooks';
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory, useParams } from 'react-router-dom';
import {
getGlobalSettingMenuItem,
getGlobalSettingsMenuWithPermission,
MenuList,
} from 'utils/GlobalSettingsUtils';
import {
getSettingPath,
getSettingsPathWithFqn,
getTeamsWithFqnPath,
} from 'utils/RouterUtils';
import './global-setting-page.style.less';
const GlobalSettingPage = () => {
const history = useHistory();
const { t } = useTranslation();
const { tab, settingCategory } = useParams<{ [key: string]: string }>();
const { permissions } = usePermissionProvider();
const { isAdminUser } = useAuth();
const menuItems: ItemType[] = useMemo(
() =>
getGlobalSettingsMenuWithPermission(permissions, isAdminUser).reduce(
(acc: ItemType[], curr: MenuList) => {
const menuItem = getGlobalSettingMenuItem({
label: curr.category,
key: curr.key,
category: curr.category,
children: curr.items,
type: 'group',
isBeta: curr.isBeta,
});
if (menuItem.children?.length) {
return [...acc, menuItem];
} else {
return acc;
}
},
[] as ItemType[]
),
[permissions]
);
const onClick: MenuProps['onClick'] = (e) => {
// As we are setting key as "category.option" and extracting here category and option
const [category, option] = e.key.split('.');
switch (option) {
case GlobalSettingOptions.TEAMS:
history.push(getTeamsWithFqnPath(TeamType.Organization));
break;
case GlobalSettingOptions.SEARCH:
history.push(
getSettingsPathWithFqn(
category,
option,
ELASTIC_SEARCH_RE_INDEX_PAGE_TABS.ON_DEMAND
)
);
break;
default:
history.push(getSettingPath(category, option));
break;
}
};
const leftPanel = menuItems.length ? (
<LeftPanelCard id="settings">
<Menu
className="custom-menu"
data-testid="global-setting-left-panel"
items={menuItems}
mode="inline"
selectedKeys={[`${settingCategory}.${tab}`]}
onClick={onClick}
/>
</LeftPanelCard>
) : null;
return (
<PageContainerV1>
<GlobalSetting />
<PageLayoutV1
className="tw-h-full tw-px-6"
leftPanel={leftPanel}
pageTitle={t('label.setting-plural')}>
<GlobalSettingRouter />
</PageLayoutV1>
</PageContainerV1>
);
};

View File

@ -20,6 +20,9 @@ import { ResourceEntity } from 'components/PermissionProvider/PermissionProvider
import { compare, Operation } from 'fast-json-patch';
import { isEmpty, isNil, isUndefined, omitBy } from 'lodash';
import { observer } from 'mobx-react';
import React, { Fragment, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory, useParams } from 'react-router-dom';
import { getAllFeeds, postFeedById, postThread } from 'rest/feedsAPI';
import {
addFollower,
@ -27,13 +30,12 @@ import {
patchMlModelDetails,
removeFollower,
} from 'rest/mlModelAPI';
import React, { Fragment, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory, useParams } from 'react-router-dom';
import AppState from '../../AppState';
import { getMlModelPath, getVersionPath } from '../../constants/constants';
import { NO_PERMISSION_TO_VIEW } from '../../constants/HelperTextUtil';
import {
NO_PERMISSION_TO_VIEW,
REACH_OUT_TO_ADMIN_FOR_ACCESS,
} from '../../constants/HelperTextUtil';
import { EntityType, TabSpecificField } from '../../enums/entity.enum';
import { FeedFilter } from '../../enums/mydata.enum';
import { CreateThread } from '../../generated/api/feed/createThread';
@ -450,7 +452,11 @@ const MlModelPage = () => {
{mlModelPermissions.ViewAll || mlModelPermissions.ViewBasic ? (
getMlModelDetail()
) : (
<ErrorPlaceHolder>{NO_PERMISSION_TO_VIEW}</ErrorPlaceHolder>
<ErrorPlaceHolder>
<p className="text-center">
{NO_PERMISSION_TO_VIEW} <br /> {REACH_OUT_TO_ADMIN_FOR_ACCESS}
</p>
</ErrorPlaceHolder>
)}
</>
)}

View File

@ -34,7 +34,10 @@ import {
getServiceDetailsPath,
getVersionPath,
} from '../../constants/constants';
import { NO_PERMISSION_TO_VIEW } from '../../constants/HelperTextUtil';
import {
NO_PERMISSION_TO_VIEW,
REACH_OUT_TO_ADMIN_FOR_ACCESS,
} from '../../constants/HelperTextUtil';
import { EntityType } from '../../enums/entity.enum';
import { ServiceCategory } from '../../enums/service.enum';
import { Pipeline } from '../../generated/entity/data/pipeline';
@ -310,7 +313,11 @@ const PipelineDetailsPage = () => {
onExtensionUpdate={handleExtensionUpdate}
/>
) : (
<ErrorPlaceHolder>{NO_PERMISSION_TO_VIEW}</ErrorPlaceHolder>
<ErrorPlaceHolder>
<p className="text-center">
{NO_PERMISSION_TO_VIEW} <br /> {REACH_OUT_TO_ADMIN_FOR_ACCESS}
</p>
</ErrorPlaceHolder>
)}
</>
)}

View File

@ -31,7 +31,10 @@ import {
getServiceDetailsPath,
getTableTabPath,
} from 'constants/constants';
import { NO_PERMISSION_TO_VIEW } from 'constants/HelperTextUtil';
import {
NO_PERMISSION_TO_VIEW,
REACH_OUT_TO_ADMIN_FOR_ACCESS,
} from 'constants/HelperTextUtil';
import { FqnPart } from 'enums/entity.enum';
import { ServiceCategory } from 'enums/service.enum';
import { compare } from 'fast-json-patch';
@ -214,7 +217,13 @@ const QueryPage = () => {
return <Loader />;
}
if (!isViewAllowed) {
return <ErrorPlaceHolder>{NO_PERMISSION_TO_VIEW}</ErrorPlaceHolder>;
return (
<ErrorPlaceHolder>
<p className="text-center">
{NO_PERMISSION_TO_VIEW} <br /> {REACH_OUT_TO_ADMIN_FOR_ACCESS}
</p>
</ErrorPlaceHolder>
);
}
if (isUndefined(query)) {

View File

@ -47,7 +47,10 @@ import {
pagingObject,
ROUTES,
} from '../../constants/constants';
import { NO_PERMISSION_TO_VIEW } from '../../constants/HelperTextUtil';
import {
NO_PERMISSION_TO_VIEW,
REACH_OUT_TO_ADMIN_FOR_ACCESS,
} from '../../constants/HelperTextUtil';
import { ACTION_TYPE } from '../../enums/common.enum';
import { OwnerType } from '../../enums/user.enum';
import { TestCase } from '../../generated/tests/testCase';
@ -363,7 +366,11 @@ const TestSuiteDetailsPage = () => {
</Row>
</PageContainerV1>
) : (
<ErrorPlaceHolder>{NO_PERMISSION_TO_VIEW}</ErrorPlaceHolder>
<ErrorPlaceHolder>
<p className="text-center">
{NO_PERMISSION_TO_VIEW} <br /> {REACH_OUT_TO_ADMIN_FOR_ACCESS}
</p>
</ErrorPlaceHolder>
)}
</>
);

View File

@ -40,7 +40,10 @@ import {
getTopicDetailsPath,
getVersionPath,
} from '../../constants/constants';
import { NO_PERMISSION_TO_VIEW } from '../../constants/HelperTextUtil';
import {
NO_PERMISSION_TO_VIEW,
REACH_OUT_TO_ADMIN_FOR_ACCESS,
} from '../../constants/HelperTextUtil';
import { EntityType, TabSpecificField } from '../../enums/entity.enum';
import { FeedFilter } from '../../enums/mydata.enum';
import { ServiceCategory } from '../../enums/service.enum';
@ -466,7 +469,11 @@ const TopicDetailsPage: FunctionComponent = () => {
onExtensionUpdate={handleExtensionUpdate}
/>
) : (
<ErrorPlaceHolder>{NO_PERMISSION_TO_VIEW}</ErrorPlaceHolder>
<ErrorPlaceHolder>
<p className="text-center">
{NO_PERMISSION_TO_VIEW} <br /> {REACH_OUT_TO_ADMIN_FOR_ACCESS}
</p>
</ErrorPlaceHolder>
)}
</>
)}

View File

@ -23,7 +23,10 @@ import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import { getServices } from 'rest/serviceAPI';
import { pagingObject, SERVICE_VIEW_CAP } from '../../constants/constants';
import { NO_PERMISSION_TO_VIEW } from '../../constants/HelperTextUtil';
import {
NO_PERMISSION_TO_VIEW,
REACH_OUT_TO_ADMIN_FOR_ACCESS,
} from '../../constants/HelperTextUtil';
import {
OPEN_METADATA,
SERVICE_CATEGORY,
@ -119,7 +122,9 @@ const ServicesPage = () => {
<Row>
<Col span={24}>
<ErrorPlaceHolder>
<p>{NO_PERMISSION_TO_VIEW}</p>
<p className="text-center">
{NO_PERMISSION_TO_VIEW} <br /> {REACH_OUT_TO_ADMIN_FOR_ACCESS}
</p>
</ErrorPlaceHolder>
</Col>
</Row>

View File

@ -42,7 +42,10 @@ import {
PAGE_SIZE_MEDIUM,
pagingObject,
} from '../../constants/constants';
import { NO_PERMISSION_TO_VIEW } from '../../constants/HelperTextUtil';
import {
NO_PERMISSION_TO_VIEW,
REACH_OUT_TO_ADMIN_FOR_ACCESS,
} from '../../constants/HelperTextUtil';
import { myDataSearchIndex } from '../../constants/Mydata.constants';
import { SearchIndex } from '../../enums/search.enum';
import { CreateTeam, TeamType } from '../../generated/api/teams/createTeam';
@ -603,7 +606,11 @@ const TeamsPage = () => {
/>
</>
) : (
<ErrorPlaceHolder>{NO_PERMISSION_TO_VIEW}</ErrorPlaceHolder>
<ErrorPlaceHolder>
<p className="text-center">
{NO_PERMISSION_TO_VIEW} <br /> {REACH_OUT_TO_ADMIN_FOR_ACCESS}
</p>
</ErrorPlaceHolder>
);
};

View File

@ -12,6 +12,7 @@
*/
import { AxiosError } from 'axios';
import { ClientErrors } from 'enums/axios.enum';
import { isEmpty, isString } from 'lodash';
import React from 'react';
import { toast } from 'react-toastify';
@ -60,7 +61,8 @@ export const showErrorToast = (
// except for principal domain mismatch errors
if (
error &&
error.response?.status === 401 &&
(error.response?.status === ClientErrors.UNAUTHORIZED ||
error.response?.status === ClientErrors.FORBIDDEN) &&
!errorMessage.includes('principal domain')
) {
return;