feat: [SIG-546]: user with viewer roles can only view saved views (#4663)

* feat: [SIG-543]: Users with VIEWER access can create/edit/delete views for logs and traces

* feat: [SIG-543]: remove extra code

* feat: [SIG-543]: role changes in the save views toolbar

* feat: [SIG-543]: role changes in the save views toolbar

* feat: remove the save feature / dashboard / alert feature for viewer roles

* feat: remove the save feature / dashboard / alert feature for viewer roles

* fix: address review comments
This commit is contained in:
Vikrant Gupta 2024-03-11 14:49:10 +05:30 committed by GitHub
parent 49aba4fb1c
commit 6b87118fc6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 331 additions and 283 deletions

View File

@ -1,3 +1,6 @@
.hide-update {
left: calc(50% - 41px) !important;
}
.explorer-update { .explorer-update {
position: fixed; position: fixed;
bottom: 16px; bottom: 16px;
@ -23,6 +26,10 @@
cursor: pointer; cursor: pointer;
} }
.hidden {
display: none;
}
.ant-divider { .ant-divider {
margin: 0; margin: 0;
height: 28px; height: 28px;
@ -55,6 +62,10 @@
.view-options, .view-options,
.actions { .actions {
.hidden {
display: none;
}
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -102,6 +113,9 @@
} }
} }
} }
.hidden {
display: none;
}
} }
.app-content { .app-content {

View File

@ -13,6 +13,7 @@ import {
Typography, Typography,
} from 'antd'; } from 'antd';
import axios from 'axios'; import axios from 'axios';
import cx from 'classnames';
import { getViewDetailsUsingViewKey } from 'components/ExplorerCard/utils'; import { getViewDetailsUsingViewKey } from 'components/ExplorerCard/utils';
import { SOMETHING_WENT_WRONG } from 'constants/api'; import { SOMETHING_WENT_WRONG } from 'constants/api';
import { QueryParams } from 'constants/query'; import { QueryParams } from 'constants/query';
@ -31,10 +32,14 @@ import { useNotifications } from 'hooks/useNotifications';
import { mapCompositeQueryFromQuery } from 'lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery'; import { mapCompositeQueryFromQuery } from 'lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery';
import { Check, ConciergeBell, Disc3, Plus, X, XCircle } from 'lucide-react'; import { Check, ConciergeBell, Disc3, Plus, X, XCircle } from 'lucide-react';
import { CSSProperties, useCallback, useMemo, useRef, useState } from 'react'; import { CSSProperties, useCallback, useMemo, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { AppState } from 'store/reducers';
import { Dashboard } from 'types/api/dashboard/getAll'; import { Dashboard } from 'types/api/dashboard/getAll';
import { Query } from 'types/api/queryBuilder/queryBuilderData'; import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
import AppReducer from 'types/reducer/app';
import { USER_ROLES } from 'types/roles';
import { import {
DATASOURCE_VS_ROUTES, DATASOURCE_VS_ROUTES,
@ -43,6 +48,9 @@ import {
saveNewViewHandler, saveNewViewHandler,
} from './utils'; } from './utils';
const allowedRoles = [USER_ROLES.ADMIN, USER_ROLES.AUTHOR, USER_ROLES.EDITOR];
// eslint-disable-next-line sonarjs/cognitive-complexity
function ExplorerOptions({ function ExplorerOptions({
disabled, disabled,
isLoading, isLoading,
@ -71,6 +79,8 @@ function ExplorerOptions({
setIsSaveModalOpen(false); setIsSaveModalOpen(false);
}; };
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
const onCreateAlertsHandler = useCallback(() => { const onCreateAlertsHandler = useCallback(() => {
history.push( history.push(
`${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent( `${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent(
@ -247,10 +257,17 @@ function ExplorerOptions({
[isDarkMode], [isDarkMode],
); );
const isEditDeleteSupported = allowedRoles.includes(role as string);
return ( return (
<> <>
{isQueryUpdated && ( {isQueryUpdated && (
<div className="explorer-update"> <div
className={cx(
isEditDeleteSupported ? '' : 'hide-update',
'explorer-update',
)}
>
<Tooltip title="Clear this view" placement="top"> <Tooltip title="Clear this view" placement="top">
<Button <Button
className="action-icon" className="action-icon"
@ -258,10 +275,13 @@ function ExplorerOptions({
icon={<X size={14} />} icon={<X size={14} />}
/> />
</Tooltip> </Tooltip>
<Divider type="vertical" /> <Divider
type="vertical"
className={isEditDeleteSupported ? '' : 'hidden'}
/>
<Tooltip title="Update this view" placement="top"> <Tooltip title="Update this view" placement="top">
<Button <Button
className="action-icon" className={cx('action-icon', isEditDeleteSupported ? ' ' : 'hidden')}
disabled={isViewUpdating} disabled={isViewUpdating}
onClick={onUpdateQueryHandler} onClick={onUpdateQueryHandler}
icon={<Disc3 size={14} />} icon={<Disc3 size={14} />}
@ -323,15 +343,16 @@ function ExplorerOptions({
<Button <Button
shape="round" shape="round"
onClick={handleSaveViewModalToggle} onClick={handleSaveViewModalToggle}
className={isEditDeleteSupported ? '' : 'hidden'}
disabled={viewsIsLoading || isRefetching} disabled={viewsIsLoading || isRefetching}
> >
<Disc3 size={16} /> Save this view <Disc3 size={16} /> Save this view
</Button> </Button>
</div> </div>
<hr /> <hr className={isEditDeleteSupported ? '' : 'hidden'} />
<div className="actions"> <div className={cx('actions', isEditDeleteSupported ? '' : 'hidden')}>
<Tooltip title="Create Alerts"> <Tooltip title="Create Alerts">
<Button <Button
disabled={disabled} disabled={disabled}

View File

@ -309,6 +309,7 @@ function DashboardsList(): JSX.Element {
/> />
</Col> </Col>
{createNewDashboard && (
<Col <Col
span={6} span={6}
style={{ style={{
@ -341,17 +342,19 @@ function DashboardsList(): JSX.Element {
</NewDashboardButton> </NewDashboardButton>
</Dropdown> </Dropdown>
</Col> </Col>
)}
</Row> </Row>
), ),
[ [
isDashboardListLoading, isDashboardListLoading,
handleSearch, handleSearch,
isFilteringDashboards, isFilteringDashboards,
searchString,
createNewDashboard,
getMenuItems, getMenuItems,
newDashboardState.loading, newDashboardState.loading,
newDashboardState.error, newDashboardState.error,
getText, getText,
searchString,
], ],
); );

View File

@ -8,7 +8,6 @@
width: calc(100% - 30px); width: calc(100% - 30px);
max-width: 736px; max-width: 736px;
.title { .title {
color: var(--bg-vanilla-100); color: var(--bg-vanilla-100);
font-size: var(--font-size-lg); font-size: var(--font-size-lg);
@ -37,7 +36,6 @@
padding: 0; padding: 0;
border: none; border: none;
background: var(--bg-ink-500); background: var(--bg-ink-500);
} }
.column-render { .column-render {
margin: 8px 0 !important; margin: 8px 0 !important;
@ -75,8 +73,11 @@
align-items: center; align-items: center;
gap: 20px; gap: 20px;
cursor: pointer; cursor: pointer;
}
.hidden {
display: none;
}
}
} }
.view-details { .view-details {
margin-top: 8px; margin-top: 8px;
@ -127,7 +128,6 @@
} }
.ant-pagination-item { .ant-pagination-item {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -141,7 +141,6 @@
font-weight: var(--font-weight-normal); font-weight: var(--font-weight-normal);
line-height: 20px; /* 142.857% */ line-height: 20px; /* 142.857% */
} }
} }
.ant-pagination-item-active { .ant-pagination-item-active {
@ -165,7 +164,7 @@
border-radius: 4px; border-radius: 4px;
border: 1px solid var(--bg-slate-500); border: 1px solid var(--bg-slate-500);
background: var(--bg-ink-400); background: var(--bg-ink-400);
box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.20); box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2);
.ant-modal-header { .ant-modal-header {
padding: 16px; padding: 16px;
@ -211,7 +210,6 @@
} }
} }
} }
} }
.ant-modal-footer { .ant-modal-footer {
@ -255,7 +253,6 @@
.lightMode { .lightMode {
.save-view-container { .save-view-container {
.save-view-content { .save-view-content {
.title { .title {
color: var(--bg-ink-500); color: var(--bg-ink-500);
} }

View File

@ -32,14 +32,20 @@ import {
} from 'lucide-react'; } from 'lucide-react';
import { ChangeEvent, useEffect, useState } from 'react'; import { ChangeEvent, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { AppState } from 'store/reducers';
import { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery'; import { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery';
import { ViewProps } from 'types/api/saveViews/types'; import { ViewProps } from 'types/api/saveViews/types';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
import AppReducer from 'types/reducer/app';
import { USER_ROLES } from 'types/roles';
import { ROUTES_VS_SOURCEPAGE, SOURCEPAGE_VS_ROUTES } from './constants'; import { ROUTES_VS_SOURCEPAGE, SOURCEPAGE_VS_ROUTES } from './constants';
import { deleteViewHandler } from './utils'; import { deleteViewHandler } from './utils';
const allowedRoles = [USER_ROLES.ADMIN, USER_ROLES.AUTHOR, USER_ROLES.EDITOR];
function SaveView(): JSX.Element { function SaveView(): JSX.Element {
const { pathname } = useLocation(); const { pathname } = useLocation();
const sourcepage = ROUTES_VS_SOURCEPAGE[pathname]; const sourcepage = ROUTES_VS_SOURCEPAGE[pathname];
@ -61,6 +67,8 @@ function SaveView(): JSX.Element {
setIsDeleteModalOpen(false); setIsDeleteModalOpen(false);
}; };
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
const handleDeleteModelOpen = (uuid: string, name: string): void => { const handleDeleteModelOpen = (uuid: string, name: string): void => {
setActiveViewKey(uuid); setActiveViewKey(uuid);
setActiveViewName(name); setActiveViewName(name);
@ -217,6 +225,9 @@ function SaveView(): JSX.Element {
// Combine time and date // Combine time and date
const formattedDateAndTime = `${formattedTime}${formattedDate}`; const formattedDateAndTime = `${formattedTime}${formattedDate}`;
const isEditDeleteSupported = allowedRoles.includes(role as string);
return ( return (
<div className="column-render"> <div className="column-render">
<div className="title-with-action"> <div className="title-with-action">
@ -234,11 +245,13 @@ function SaveView(): JSX.Element {
<div className="action-btn"> <div className="action-btn">
<PenLine <PenLine
size={14} size={14}
className={isEditDeleteSupported ? '' : 'hidden'}
onClick={(): void => handleEditModelOpen(view, bgColor)} onClick={(): void => handleEditModelOpen(view, bgColor)}
/> />
<Compass size={14} onClick={(): void => handleRedirectQuery(view)} /> <Compass size={14} onClick={(): void => handleRedirectQuery(view)} />
<Trash2 <Trash2
size={14} size={14}
className={isEditDeleteSupported ? '' : 'hidden'}
color={Color.BG_CHERRY_500} color={Color.BG_CHERRY_500}
onClick={(): void => handleDeleteModelOpen(view.uuid, view.name)} onClick={(): void => handleDeleteModelOpen(view.uuid, view.name)}
/> />