Skip to content

Conversation

@pd-redis
Copy link
Collaborator

@pd-redis pd-redis commented Nov 12, 2025

What

This PR refactors the Redis Cloud and Redis Cluster database discovery UI by extracting inline table column definitions
into dedicated, reusable components. These changes bring the current implementation closer to Figma designs while
improving code maintainability, testability, and consistency across the application.

Key Changes:

1. Column Definition Extraction

  • For Redis Cluster, Redis Cloud Subscriptions, Redis Cloud Databases and Redis Cloud Databases Result extracted column definitions into dedicated, reusable functions.

2. Custom Hook Extraction

  • Created hooks for Redis Cluster and Redis Cloud database discovery logic.

3. Storybook Stories

  • Added comprehensive stories for RedisClusterDatabases (15+ variants)
  • Added stories for RedisCloudDatabases with different states
  • Added stories for RedisCloudSubscriptions with multipage examples
  • Added stories for RedisCloudDatabasesResult with success/error states
  • Created factory functions for mock data generation

4. UI/UX Improvements (Aligned with Figma Designs)

  • Improved DatabaseListOptions component with better styling and stories to match design specs
  • Enhanced MessageBar component with variant support and comprehensive tests
  • Fixed HoverContent to not wrap non-text titles per design guidelines
  • Updated ClusterConnectionForm layout to align with Figma mockups
  • Improved endpoint display with consistent copy functionality matching design system
  • Updated column sizing and spacing to match Figma specifications

Tech Decisions:

  1. Column Component Pattern: Each column is now a factory function returning a ColumnDef
  2. Shared Components: Leveraged existing CopyBtn, CopyTextContainer, and CellText components for consistency
  3. Factory Pattern: Created factory functions for mock data to support comprehensive Storybook stories
  4. Hook Extraction: Moved complex table logic to custom hooks, keeping page components focused on rendering

Testing

Manual Testing:

  • Verified Redis Cluster database discovery flow works correctly
    • Note: To test Redis Cluster, a Redis Enterprise installation is needed. One can be found in the tests/e2e/rte/redis-enterprise folder
  • Verified Redis Cloud subscription selection and database discovery
  • Verified result pages display correctly with success/error states
  • Tested column sorting, selection, and copy functionality
  • Verified conditional column visibility (Capabilities, Options)
  • Tested all Storybook stories render correctly

Automation Tests:

  • Added comprehensive MessageBar component tests covering all variants and states
  • Existing tests pass without modification (no breaking changes)
  • Updated test data-testid attributes remain consistent

Visual Changes:

  • Enhanced database options display with better spacing
  • Updated message bar to support success/attention variants
  • Consistent tooltip positioning and styling across all tables

Screenshots/Recordings:

Redis Cloud

Screen.Recording.2025-11-12.at.16.15.39.mov

Sentinel Cluster

Screen.Recording.2025-11-12.at.16.17.24.mov

Redis Cluster

Screen.Recording.2025-11-12.at.16.16.37.mov

pd-redis added 30 commits May 7, 2025 12:28
# Conflicts:
#	redisinsight/ui/src/components/connectivity-error/ConnectivityError.tsx
#	redisinsight/ui/src/components/inline-item-editor/InlineItemEditor.tsx
#	redisinsight/ui/src/components/item-list/components/delete-action/DeleteAction.tsx
#	redisinsight/ui/src/components/item-list/components/export-action/ExportAction.tsx
#	redisinsight/ui/src/components/query/query-actions/QueryActions.tsx
#	redisinsight/ui/src/components/query/query-card/QueryCardHeader/QueryCardHeader.tsx
#	redisinsight/ui/src/components/side-panels/panels/enablement-area/EnablementArea/components/InternalPage/InternalPage.tsx
#	redisinsight/ui/src/pages/browser/components/add-key/AddKeyHash/AddKeyHash.tsx
#	redisinsight/ui/src/pages/browser/components/add-key/AddKeyList/AddKeyList.tsx
#	redisinsight/ui/src/pages/browser/components/add-key/AddKeyReJSON/AddKeyReJSON.tsx
#	redisinsight/ui/src/pages/browser/components/add-key/AddKeySet/AddKeySet.tsx
#	redisinsight/ui/src/pages/browser/components/add-key/AddKeyStream/AddKeyStream.tsx
#	redisinsight/ui/src/pages/browser/components/add-key/AddKeyString/AddKeyString.tsx
#	redisinsight/ui/src/pages/browser/components/add-key/AddKeyZset/AddKeyZset.tsx
#	redisinsight/ui/src/pages/browser/modules/key-details/components/rejson-details/components/edit-entire-item-action/EditEntireItemAction.tsx
#	redisinsight/ui/src/pages/browser/modules/key-details/components/set-details/add-set-members/AddSetMembers.tsx
#	redisinsight/ui/src/pages/database-analysis/components/header/Header.tsx
#	redisinsight/ui/src/pages/home/components/database-list-header/DatabaseListHeader.tsx
#	redisinsight/ui/src/pages/home/components/database-manage-tags-modal/ManageTagsModal.tsx
#	redisinsight/ui/src/pages/settings/components/cloud-settings/CloudSettings.tsx
#	redisinsight/ui/src/pages/settings/components/cloud-settings/components/user-api-keys-table/UserApiKeysTable.tsx
#	redisinsight/ui/src/pages/slow-log/components/Actions/Actions.tsx
# Conflicts:
#	redisinsight/ui/src/pages/home/components/manual-connection/manual-connection-form/ManualConnectionForm.tsx
Copy link

@jit-ci jit-ci bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ Jit has detected 1 important finding in this PR that you should review.
The finding is detailed below as a comment.
It’s highly recommended that you fix this security issue before merge.

Repository Risks:

  • Critical Severity Findings: Indicates that the resource has critical severity security findings that need immediate action.
  • Internally Accessible: Accessible only within the internal network, reducing exposure to external threats but still requiring proper controls.
  • Database Integration: Connects to a database, often involving sensitive data that must be securely managed.
  • Production: Critical as it operates in a live production environment, directly impacting users and business operations.

Repository Context:

graph LR
    GitHub$Repository_U23_redis/RedisInsight["GitHub Repository<br/>redis/RedisInsight"]:::GitHub$Repository
    DBIntegration_U23_redis["DBIntegration<br/>redis"]:::DBIntegration
    GitHub$Actions_U23_pipeline_U2D_build_U2D_windows_U2E_yml["GitHub Actions<br/>pipeline-build-windows.yml"]:::GitHub$Actions
    GitHub$Repository_U23_redis/RedisInsight -- "Is accessible to" --> DBIntegration_U23_redis
    GitHub$Repository_U23_redis/RedisInsight -- "Has" --> GitHub$Actions_U23_pipeline_U2D_build_U2D_windows_U2E_yml
Loading

@github-actions
Copy link
Contributor

github-actions bot commented Nov 12, 2025

Code Coverage - Frontend unit tests

St.
Category Percentage Covered / Total
🟢 Statements 82.73% 20709/25031
🟡 Branches 67.79% 8805/12989
🟡 Functions 77.58% 5611/7233
🟢 Lines 83.14% 20280/24393

Test suite run success

5289 tests passing in 689 suites.

Report generated by 🧪jest coverage report action from 71d37c6

@CLAassistant
Copy link

CLAassistant commented Nov 12, 2025

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
3 out of 4 committers have signed the CLA.

✅ KrumTy
✅ dantovska
✅ pd-redis
❌ Kristiyan Ivanov


Kristiyan Ivanov seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

… user permissions

Add platform specification and ensure redis user/group exist before running container
},
}

const backup12hInstances = RedisCloudInstanceFactoryOptionsBackupSchedule(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: What is the difference between these 3 scenarios, because I don't see it reflected visually.

The only thing I've noticed to differentiate between the scenarios is the data in the table, which is a result of the factory, but that's all.

Backup Hourly

Image

Backup Every 6 hours

Image

Backup every 12 hours

Image

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You will need to dig in to what these options mean, in any case these are just added for completeness

columns: withoutModulesColumns,
},
}
// Free vs Paid
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Do we have any underlying logic that depends on that, or is it just for completeness of the examples?

In the other examples, I see some visual changes in the table (like hiding the capabilities/options columns), but for this one, the only diff is on a factory level.

Free

Image

Paid

Image

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, just exhausting possible rendering variants. This is why you would use a story, to stick in a bunch of random data and see a bunch of display variations


if (!shouldShowOptions) {
columns.splice(
columns.findIndex((col) => col.id === 'options'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I suggest replacing these magic strings with a reusable enum, so that we can easily maintain them in the future. It will also help us apply changes and trace all the places where this functionality is used, if it's not just a regular string.

A place with all the available columns

export enum CloudDatabaseTableColumns {
    // ...
    Modules = 'modules',
    Options = 'options'
    // ...
}

Then, in the column defintion, we can use the enum, instead of the hardcoded string

export const OptionsColumn = (
  instances: InstanceRedisCloud[],
): ColumnDef<InstanceRedisCloud> => {
  return {
    id: CloudDatabaseTableColumns.Options,
    header: 'Options',
    ...
  }
}

And later, whenever we do some checks based on the column ID, we can simply use the enums.

if (!shouldShowOptions) {
    columns.splice(
        columns.findIndex((col) => col.id === CloudDatabaseTableColumns.Options),

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was not the best approach to this specific task anyway, but I partially accept your comment

ownerEmail: '[email protected]',
}

const instancesMock: InstanceRedisCloud[] = [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see you created RedisCloudInstanceFactory, so is it intentional that you don't use it here?


const emptyColumns = colFactory([])

const accountMock: RedisCloudAccount = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip: It's a very small one, but it may be handy to introduce a factory for it as well, to follow the same patterns as the rest.

const handleCopy = (text = '') => {
navigator.clipboard.writeText(text)
}
import { handleCopy } from 'uiSrc/utils'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small, but handy. Kudos 🍕

},
]}
paginationEnabled={items.length > 10}
pageSizes={[5, 10, 25, 50, 100]}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip: I see this config in a few places already, maybe we should consider moving it out as a shared constant?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, I didn't move it just to bring it to the discussion table. Probably a designer should say if we need to have it at all or rely on default implementation

},
}

const mockInstance = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we use RedisCloudInstanceFactory here, or the type of this mock is different, and we don't have a factory for it yet?

If that's the case, I would suggest creating one for it, like the rest we already have.


import ManualConnectionForm from './'

const conn = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip: One more candidate for a reusable factory.

@@ -1,6 +1,12 @@
FROM redislabs/redis:6.2.8-50
FROM redislabs/redis:8.0.2-17
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how you handle it usually, but I saw plenty of other Dockerfiles with different versions for each stack, and I wonder do we want to keep the old version for some backward compatibility checks.

Or, it's not necessary in the current scenario.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should test with latest version mainly, but I will revert this one

Copy link
Collaborator

@KrumTy KrumTy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am requesting changes (I don't do that usually) for a couple of reasons

  1. I do not agree with the way table columns have been handled. See my comment in column-definitions/AlertColumn.tsx for more context. In one of my PRs you had a suggestion for creating a more generic cell controller, which I did for RDI - check RdiInstancesListCell usage here. None of the bellow items is a component and none of them have tests, even though some should.
Screenshot 2025-11-14 at 11 52 18
  1. I think this PR is very large. I see multiple tables and I don't think each one depends on the other. 1 PR per table would've been ideal. This is not a blocker at this stage, however.
  2. Storybook components - I think these should be in a separate PR as well, after the main one has been approved. Or we can agree not to review them. We do not currently have a rule as to when to add them even. This is something that should be discussed first.

Comment on lines +30 to +43
0: { bg: '#293152' },
1: { bg: '#323e6c' },
2: { bg: '#465282' },
3: { bg: '#606c98' },
4: { bg: '#737fa8' },
5: { bg: '#8f99bc', fg: customFgDarkColor },
6: { bg: '#adb5d3', fg: customFgDarkColor },
7: { bg: '#cdd4ea', fg: customFgDarkColor },
},
light: {
0: { bg: '#587AB2' },
1: { bg: '#6A8BC1' },
2: { bg: '#97B4E3', fg: customFgLightColor },
3: { bg: '#ADC5ED', fg: customFgLightColor },
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lots of custom colors, can you elaborate? Is this all custom for icons in db list options or it's more of a generic config for some icons?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, these are all custom colors. Provided that we update redis-ui, we will have the option to replace them with redis-ui colors, but until then (next pr at some point) we need this.
Having them co-located to the only place they are used will make the change simple once we update

@@ -0,0 +1,108 @@
import React from 'react'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for large PRs involving refactors it would be nice to have a note on what is just copied over and not new

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

git shows what has been renamed and changed - sometimes, but ./index.ts became styles.ts because it is more smanticaly correct.

Comment on lines 10 to 23
const renderTitle = (title: React.ReactNode) => {
if (!title) {
return null
}
if (typeof title === 'string') {
return <Title size="XS">{title}</Title>
}
return title
}
export const HoverContent = ({ title, content }: RiTooltipContentProps) => {
return (
<Col gap="s">
{renderTitle(title)}
{content}
Copy link
Collaborator

@KrumTy KrumTy Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks a bit odd and now requires testing. Is it really used with that many title variants? I'd suggest refactoring it in a way that renderTitle can be dropped (lets write components and not functions that return jsx)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It often causes console warnings for invalid dom nesting

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tests are added


const DatabaseListOptions = ({ options }: Props) => {
const { theme } = useContext(ThemeContext)
const Tooltip = ({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's too much stuff going on in this file. Multiple components/styles and no tests changed is sus

import { AlertStatusContent } from 'uiSrc/pages/autodiscover-cloud/components/AlertStatusContent'
import styles from 'uiSrc/pages/autodiscover-cloud/redis-cloud-subscriptions/styles.module.scss'

export const AlertColumn = (): ColumnDef<RedisCloudSubscription> => {
Copy link
Collaborator

@KrumTy KrumTy Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very misleading. export const AlertColumn inside a AlertColumn.tsx file - everyone would assume this to be a component. IMO the columns config should be a separated, the cell itself - this is a component and can be a separate file. See this as an example https://github.com/redis/RedisInsight/blob/main/redisinsight/ui/src/pages/rdi/home/components/rdi-instances-list/RdiInstancesList.config.tsx#L13

EDIT: Just so that I am more clear about the idea:

  1. Define all columns as a static array (example)
  2. You can later filter this columns array, if you want to show only some of the columns
  3. Cells - declare those as custom/reusable components that implement the same interface. They can have styles/tests/etc. They can access state/context and so on. (example)

This is a very robust solution with a clear separation of concerns. Generating columns run time and mixing them with components is not.

Comment on lines +102 to 106
const onSelectionChange = (selection: RowSelectionState) => {
// Map rowSelection state to the selected items list using uid as row id
const selectedItems = items.filter((i) => selection[`${i.uid}`])
setSelection(selectedItems)
}
Copy link
Collaborator

@KrumTy KrumTy Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useMemo is preferable in this case

EDIT: to elaborate - this is only if you want to control row selection externally e.g. trigger an event that unselects all items if X is clicked. Without passing rowSelection you can't do that, but if you don't need it - you can keep current implementation

if (instancesAdded.length) {
return (
<RedisClusterDatabasesResult
instances={instancesAdded || []}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of using new [], declare export const EMPTY_ARRAY: any[] = [] somewhere and use that

preferably use it in the hook even

Comment on lines +121 to +128
paginationEnabled={items.length > 10}
stripedRows
pageSizes={[5, 10, 25, 50, 100]}
emptyState={() => (
<Col centered full>
<FlexItem padding={13}>
<Text size="L">{message}</Text>
</FlexItem>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reuse, also 10 - extract as const too

Comment on lines +92 to +94
const [columns, columnsResult] = useMemo(
() => colFactory(instances),
[instances],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't need to recreate all columns if instances change, there's only one column's cell that depends on it - use context/redux instead

# redis enterprise
redis-enterprise:
logging: *logging
platform: linux/amd64
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be part of this PR?

@pd-redis pd-redis requested a review from KrumTy November 14, 2025 14:17
Copy link

@jit-ci jit-ci bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ Jit has detected 1 important finding in this PR that you should review.
The finding is detailed as a comment.
It’s highly recommended that you fix this security issue before merge.

Until now, you ignored/fixed 1 finding.

@@ -1,4 +1,4 @@
FROM redislabs/redis:6.2.8-50
FROM redislabs/redis:8.0.2-17
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security control: Docker Scan

Image User Should Not Be 'Root'

Running containers with 'root' user can lead to a container escape situation. It is a best practice to run containers as non-root users, which can be done by adding a 'USER' statement to the Dockerfile.

Severity: HIGH

Learn more about this issue


Fix suggestion:

This fix suggestion was generated by Jit. Please note that the suggestion might not always fit every use case. It is highly recommended that you check and review it before merging.

Suggestion guidelines

  • First of all, check if your container is running as a root user. In most of the cases, you can do it by running a command like this: docker run <image> whoami. If it returns root, then you should consider using a non-root user, by following one of the next steps:
    • If a non-root user already exists in your container, consider using it.
    • If not, you can create a new user by adding a USER command to the Dockerfile, with a non-root user as argument, for example: USER <non-root-user-name>.
Suggested change
FROM redislabs/redis:8.0.2-17
FROM redislabs/redis:8.0.2-17
RUN addgroup --system <group>
RUN adduser --system <user> --ingroup <group>
USER <user>:<group>

Why should you fix this issue?
This Dockerfile introduces a container vulnerability. In a production environment, using insecure container configurations or outdated base images can lead to significant security risks. If an attacker exploits a vulnerability in the container, it could compromise the entire application or lead to unauthorized access.


Jit Bot commands and options (e.g., ignore issue)

You can trigger Jit actions by commenting on this PR review:

  • #jit_ignore_fp Ignore and mark this specific single instance of finding as “False Positive”
  • #jit_ignore_accept Ignore and mark this specific single instance of finding as “Accept Risk”
  • #jit_ignore_type_in_file Ignore any finding of type "Image user should not be 'root'" in tests/e2e/rte/redis-enterprise/Dockerfile; future occurrences will also be ignored.
  • #jit_undo_ignore Undo ignore command

Extract complex cell renderers from cloud column definitions into 5 standalone components (Alert, Database, Endpoint, MessageResult, Subscription) with types and tests. Handle optional props in MessageResultCell for undefined status/message values.
…iscovery hooks into hooks/utils/types with comprehensive tests
…covery stories and consolidate duplicate stories
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants