Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions redisinsight/ui/src/components/item-list/ItemList.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,11 @@ describe('ItemList', () => {

expect(screen.queryByText('Export')).not.toBeInTheDocument()
})

it('should add hideSelectableCheckboxes class when isSelectable = false', async () => {
const { container } = render(<ItemList {...mockedProps} hideSelectableCheckboxes />)
const div = container.querySelector('.itemList')

expect(div).toHaveClass('hideSelectableCheckboxes')
})
})
8 changes: 5 additions & 3 deletions redisinsight/ui/src/components/item-list/ItemList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ export interface Props<T> {
loading: boolean
data: T[]
onTableChange: ({ sort, page }: Criteria<T>) => void
sort: PropertySort
sort: PropertySort,
hideSelectableCheckboxes?: boolean,
}

function ItemList<T extends { id: string; visible?: boolean }>({
Expand All @@ -44,7 +45,8 @@ function ItemList<T extends { id: string; visible?: boolean }>({
loading,
data: instances,
onTableChange,
sort
sort,
hideSelectableCheckboxes,
}: Props<T>) {
const [columns, setColumns] = useState<EuiTableFieldDataColumnType<T>[]>(columnsProp)
const [selection, setSelection] = useState<T[]>([])
Expand Down Expand Up @@ -175,7 +177,7 @@ function ItemList<T extends { id: string; visible?: boolean }>({
`

return (
<div className="itemList" ref={containerTableRef}>
<div className={`itemList ${hideSelectableCheckboxes ? 'hideSelectableCheckboxes' : ''}`} ref={containerTableRef}>
<EuiInMemoryTable
ref={tableRef}
items={instances.filter(({ visible = true }) => visible)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,26 @@ describe('INFINITE_MESSAGES', () => {
expect(onCancel).toBeCalled()
})
})

describe('DATABASE_IMPORT_FORBIDDEN', () => {
it('should render message', () => {
const { Inner } = INFINITE_MESSAGES.DATABASE_IMPORT_FORBIDDEN(jest.fn())
expect(render(<>{Inner}</>)).toBeTruthy()
})

it('should call onClose', () => {
const onClose = jest.fn()
const { Inner } = INFINITE_MESSAGES.DATABASE_IMPORT_FORBIDDEN(onClose)
render(<>{Inner}</>)

fireEvent.click(screen.getByTestId('database-import-forbidden-notification-ok-btn'))
fireEvent.mouseUp(screen.getByTestId('database-import-forbidden-notification'))
fireEvent.mouseDown(screen.getByTestId('database-import-forbidden-notification'))

expect(onClose).toBeCalled()
})
})

describe('SUBSCRIPTION_EXISTS', () => {
it('should render message', () => {
const { Inner } = INFINITE_MESSAGES.SUBSCRIPTION_EXISTS(jest.fn())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiLink,
EuiLoadingSpinner,
EuiSpacer,
EuiText,
Expand All @@ -29,6 +30,7 @@ export enum InfiniteMessagesIds {
oAuthSuccess = 'oAuthSuccess',
autoCreateDb = 'autoCreateDb',
databaseExists = 'databaseExists',
databaseImportForbidden = 'databaseImportForbidden',
subscriptionExists = 'subscriptionExists',
appUpdateAvailable = 'appUpdateAvailable',
pipelineDeploySuccess = 'pipelineDeploySuccess'
Expand Down Expand Up @@ -219,6 +221,57 @@ export const INFINITE_MESSAGES = {
</div>
)
}),
DATABASE_IMPORT_FORBIDDEN: (onClose?: () => void) => ({
id: InfiniteMessagesIds.databaseImportForbidden,
Inner: (
<div
role="presentation"
onMouseDown={(e) => { e.preventDefault() }}
onMouseUp={(e) => { e.preventDefault() }}
data-testid="database-import-forbidden-notification"
>
<EuiTitle className="infiniteMessage__title">
<span>
Unable to import Cloud database.
</span>
</EuiTitle>
<EuiText size="xs">
Adding your Redis Cloud database to Redis Insight is disabled due to
a setting restricting database connection management.

<EuiSpacer size="m" />

Log in to
{' '}
<EuiLink
target="_blank"
color="text"
external={false}
tabIndex={-1}
href="https://cloud.redis.io/#/databases?utm_source=redisinsight&utm_medium=main&utm_campaign=disabled_db_management"
>
Redis Cloud
</EuiLink>
{' '}
to check your database.
</EuiText>
<EuiSpacer size="m" />
<EuiFlexGroup justifyContent="flexEnd" gutterSize="none">
<EuiFlexItem grow={false}>
<EuiButton
fill
size="s"
color="secondary"
onClick={() => onClose?.()}
data-testid="database-import-forbidden-notification-ok-btn"
>
Ok
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</div>
)
}),
SUBSCRIPTION_EXISTS: (onSuccess?: () => void, onClose?: () => void) => ({
id: InfiniteMessagesIds.subscriptionExists,
Inner: (
Expand Down
28 changes: 28 additions & 0 deletions redisinsight/ui/src/components/oauth/oauth-jobs/OAuthJobs.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,34 @@ describe('OAuthJobs', () => {
)
})

it('should call addInfiniteNotification and removeInfiniteNotification when errorCode is 11_115', async () => {
const error = {
errorCode: CustomErrorCodes.CloudDatabaseImportForbidden,
};
(oauthCloudJobSelector as jest.Mock).mockImplementation(() => ({
status: ''
}))

const { rerender } = render(<OAuthJobs />);

(oauthCloudJobSelector as jest.Mock).mockImplementation(() => ({
status: CloudJobStatus.Failed,
error,
}))

rerender(<OAuthJobs />)

const expectedActions = [
addInfiniteNotification(INFINITE_MESSAGES.DATABASE_IMPORT_FORBIDDEN()),
setSSOFlow(),
setSocialDialogState(null),
removeInfiniteNotification(InfiniteMessagesIds.oAuthProgress),
]
expect(clearStoreActions(store.getActions())).toEqual(
clearStoreActions(expectedActions)
)
})

it('should call logoutUser when statusCode is 401', async () => {
const mockDatabaseId = '123'
const error = {
Expand Down
16 changes: 16 additions & 0 deletions redisinsight/ui/src/components/oauth/oauth-jobs/OAuthJobs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,17 @@ const OAuthJobs = () => {

break

case CustomErrorCodes.CloudDatabaseImportForbidden:
sendEventTelemetry({
event: TelemetryEvent.CLOUD_IMPORT_DATABASE_FORBIDDEN,
})

dispatch(addInfiniteNotification(
INFINITE_MESSAGES.DATABASE_IMPORT_FORBIDDEN(closeDatabaseImportForbidden)
))

break

case CustomErrorCodes.CloudSubscriptionAlreadyExistsFree:
dispatch(addInfiniteNotification(INFINITE_MESSAGES.SUBSCRIPTION_EXISTS(
() => createFreeDatabase(subscriptionId),
Expand Down Expand Up @@ -123,6 +134,11 @@ const OAuthJobs = () => {
}))
}

const closeDatabaseImportForbidden = () => {
dispatch(setSSOFlow())
dispatch(removeInfiniteNotification(InfiniteMessagesIds.databaseImportForbidden))
}

const closeImportDatabase = () => {
sendEventTelemetry({
event: TelemetryEvent.CLOUD_IMPORT_EXISTING_DATABASE_FORM_CLOSED,
Expand Down
1 change: 1 addition & 0 deletions redisinsight/ui/src/constants/customErrorCodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export enum CustomErrorCodes {
CloudTaskNotFound = 11_112,
CloudJobNotFound = 11_113,
CloudSubscriptionAlreadyExistsFree = 11_114,
CloudDatabaseImportForbidden = 11_115,

// General database errors [11200, 11299]
DatabaseAlreadyExists = 11_200,
Expand Down
1 change: 1 addition & 0 deletions redisinsight/ui/src/constants/featureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export enum FeatureFlags {
rdi = 'redisDataIntegration',
hashFieldExpiration = 'hashFieldExpiration',
enhancedCloudUI = 'enhancedCloudUI',
databaseManagement = 'databaseManagement',
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { act, cleanup, fireEvent, mockedStore, render, screen } from 'uiSrc/util
import { CREATE_CLOUD_DB_ID } from 'uiSrc/pages/home/constants'
import { setSSOFlow } from 'uiSrc/slices/instances/cloud'
import { setSocialDialogState } from 'uiSrc/slices/oauth/cloud'
import {appFeatureFlagsFeaturesSelector} from 'uiSrc/slices/app/features'
import DatabasesListWrapper, { Props } from './DatabasesListWrapper'

const mockedProps = mock<Props>()
Expand All @@ -33,7 +34,10 @@ jest.mock('uiSrc/slices/app/features', () => ({
appFeatureFlagsFeaturesSelector: jest.fn().mockReturnValue({
cloudSso: {
flag: true
}
},
databaseManagement: {
flag: true,
},
}),
}))

Expand Down Expand Up @@ -229,4 +233,17 @@ describe('DatabasesListWrapper', () => {

(sendEventTelemetry as jest.Mock).mockRestore()
})

it('should hide management buttons when databaseManagement feature flag is disabled', async () => {
(appFeatureFlagsFeaturesSelector as jest.Mock).mockReturnValue({
databaseManagement: {
flag: false
}
})

const { queryByTestId } = render(<DatabasesListWrapper {...instance(mockedProps)} instances={mockInstances} />)

expect(queryByTestId(/^edit-instance-/i)).not.toBeInTheDocument()
expect(queryByTestId(/^delete-instance-/i)).not.toBeInTheDocument()
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import { appFeatureFlagsFeaturesSelector } from 'uiSrc/slices/app/features'
import { getUtmExternalLink } from 'uiSrc/utils/links'
import { CREATE_CLOUD_DB_ID, HELP_LINKS } from 'uiSrc/pages/home/constants'

import { FeatureFlagComponent } from 'uiSrc/components'
import DbStatus from '../db-status'

import styles from './styles.module.scss'
Expand Down Expand Up @@ -88,7 +89,10 @@ const DatabasesListWrapper = (props: Props) => {
const { theme } = useContext(ThemeContext)

const { contextInstanceId } = useSelector(appContextSelector)
const { [FeatureFlags.cloudSso]: cloudSsoFeature } = useSelector(appFeatureFlagsFeaturesSelector)
const {
[FeatureFlags.cloudSso]: cloudSsoFeature,
[FeatureFlags.databaseManagement]: databaseManagementFeature,
} = useSelector(appFeatureFlagsFeaturesSelector)

const [width, setWidth] = useState(0)
const [, forceRerender] = useState({})
Expand Down Expand Up @@ -447,26 +451,28 @@ const DatabasesListWrapper = (props: Props) => {
</EuiLink>
</EuiToolTip>
)}
<EuiButtonIcon
iconType="pencil"
className="editInstanceBtn"
aria-label="Edit instance"
data-testid={`edit-instance-${instance.id}`}
onClick={() => handleClickEditInstance(instance)}
/>
<PopoverDelete
header={formatLongName(instance.name, 50, 10, '...')}
text="will be removed from Redis Insight."
item={instance.id}
suffix={suffix}
deleting={deletingIdRef.current}
closePopover={closePopover}
updateLoading={false}
showPopover={showPopover}
handleDeleteItem={() => handleDeleteInstance(instance)}
handleButtonClick={() => handleClickDeleteInstance(instance)}
testid={`delete-instance-${instance.id}`}
/>
<FeatureFlagComponent name={FeatureFlags.databaseManagement}>
<EuiButtonIcon
iconType="pencil"
className="editInstanceBtn"
aria-label="Edit instance"
data-testid={`edit-instance-${instance.id}`}
onClick={() => handleClickEditInstance(instance)}
/>
<PopoverDelete
header={formatLongName(instance.name, 50, 10, '...')}
text="will be removed from Redis Insight."
item={instance.id}
suffix={suffix}
deleting={deletingIdRef.current}
closePopover={closePopover}
updateLoading={false}
showPopover={showPopover}
handleDeleteItem={() => handleDeleteInstance(instance)}
handleButtonClick={() => handleClickDeleteInstance(instance)}
testid={`delete-instance-${instance.id}`}
/>
</FeatureFlagComponent>
</>
)
},
Expand Down Expand Up @@ -507,6 +513,7 @@ const DatabasesListWrapper = (props: Props) => {
getSelectableItems={(item) => item.id !== 'create-free-cloud-db'}
onTableChange={onTableChange}
sort={sortingRef.current}
hideSelectableCheckboxes={!databaseManagementFeature?.flag}
/>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ $breakpoint-l: 1400px;
visibility: hidden;
}
}

:global(.hideSelectableCheckboxes .euiCheckbox) {
visibility: hidden;
}
}

.cloudIcon {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ jest.mock('uiSrc/slices/app/features', () => ({
appFeatureFlagsFeaturesSelector: jest.fn().mockReturnValue({
enhancedCloudUI: {
flag: false
}
},
databaseManagement: {
flag: true,
},
}),
}))

Expand Down Expand Up @@ -66,4 +69,22 @@ describe('DatabaseListHeader', () => {

expect(screen.getByTestId('promo-btn')).toBeInTheDocument()
})

it('should show "create database" button when database management feature flag is enabled', () => {
const { queryByTestId } = render(<DatabaseListHeader {...instance(mockedProps)} />)

expect(queryByTestId('add-redis-database-short')).toBeInTheDocument()
})

it('should hide "create database" button when database management feature flag is disabled', () => {
(appFeatureFlagsFeaturesSelector as jest.Mock).mockReturnValue({
databaseManagement: {
flag: false
}
})

const { queryByTestId } = render(<DatabaseListHeader {...instance(mockedProps)} />)

expect(queryByTestId('add-redis-database-short')).not.toBeInTheDocument()
})
})
Loading