From fe14ebed6c3742ca040fac33cc1d61eadc81d509 Mon Sep 17 00:00:00 2001 From: zalenskiSofteq Date: Tue, 25 Jul 2023 18:59:22 +0200 Subject: [PATCH 1/4] #RI-4506 - Update recommendations without the code change --- .../ui/src/components/config/Config.spec.tsx | 3 + .../ui/src/components/config/Config.tsx | 2 + .../LiveTimeRecommendations.spec.tsx | 25 +- .../LiveTimeRecommendations.tsx | 7 +- .../recommendation/Recommendation.spec.tsx | 35 +- .../recommendation/Recommendation.tsx | 5 +- .../components/vote-option/VoteOption.tsx | 7 +- redisinsight/ui/src/constants/api.ts | 1 + .../constants/dbAnalysisRecommendations.json | 1655 ---------------- .../constants/mocks/mock-recommendations.ts | 1657 +++++++++++++++++ .../DatabaseAnalysisTabs.spec.tsx | 24 +- .../data-nav-tabs/DatabaseAnalysisTabs.tsx | 6 +- .../Recommendations.spec.tsx | 24 + .../recommendations-view/Recommendations.tsx | 8 +- .../src/slices/interfaces/recommendations.ts | 1 + .../slices/recommendations/recommendations.ts | 36 +- .../ui/src/utils/recommendation/utils.tsx | 18 +- .../utils/tests/recommendation/utils.spec.tsx | 3 +- 18 files changed, 1802 insertions(+), 1715 deletions(-) delete mode 100644 redisinsight/ui/src/constants/dbAnalysisRecommendations.json create mode 100644 redisinsight/ui/src/constants/mocks/mock-recommendations.ts diff --git a/redisinsight/ui/src/components/config/Config.spec.tsx b/redisinsight/ui/src/components/config/Config.spec.tsx index c4f4aa65de..985d9132f0 100644 --- a/redisinsight/ui/src/components/config/Config.spec.tsx +++ b/redisinsight/ui/src/components/config/Config.spec.tsx @@ -17,6 +17,7 @@ import { getRedisCommands } from 'uiSrc/slices/app/redis-commands' import { ONBOARDING_FEATURES } from 'uiSrc/components/onboarding-features' import { getWBGuides } from 'uiSrc/slices/workbench/wb-guides' import { getWBTutorials } from 'uiSrc/slices/workbench/wb-tutorials' +import { getContentRecommendations } from 'uiSrc/slices/recommendations/recommendations' import Config from './Config' let store: typeof mockedStore @@ -61,6 +62,7 @@ describe('Config', () => { processCliClient(), getRedisCommands(), getNotifications(), + getContentRecommendations(), getWBGuides(), getWBTutorials(), getFeatureFlags(), @@ -94,6 +96,7 @@ describe('Config', () => { processCliClient(), getRedisCommands(), getNotifications(), + getContentRecommendations(), getWBGuides(), getWBTutorials(), getFeatureFlags(), diff --git a/redisinsight/ui/src/components/config/Config.tsx b/redisinsight/ui/src/components/config/Config.tsx index d9c99a8c96..3c13ef4e6a 100644 --- a/redisinsight/ui/src/components/config/Config.tsx +++ b/redisinsight/ui/src/components/config/Config.tsx @@ -30,6 +30,7 @@ import { fetchRedisCommandsInfo } from 'uiSrc/slices/app/redis-commands' import { fetchGuides } from 'uiSrc/slices/workbench/wb-guides' import { fetchTutorials } from 'uiSrc/slices/workbench/wb-tutorials' import { ONBOARDING_FEATURES } from 'uiSrc/components/onboarding-features' +import { fetchContentRecommendations } from 'uiSrc/slices/recommendations/recommendations' import favicon from 'uiSrc/assets/favicon.ico' const SETTINGS_PAGE_PATH = '/settings' @@ -48,6 +49,7 @@ const Config = () => { dispatch(fetchUnsupportedCliCommandsAction()) dispatch(fetchRedisCommandsInfo()) dispatch(fetchNotificationsAction()) + dispatch(fetchContentRecommendations()) // get guides & tutorials dispatch(fetchGuides()) diff --git a/redisinsight/ui/src/components/live-time-recommendations/LiveTimeRecommendations.spec.tsx b/redisinsight/ui/src/components/live-time-recommendations/LiveTimeRecommendations.spec.tsx index 961f1d7dd5..5aab9659f6 100644 --- a/redisinsight/ui/src/components/live-time-recommendations/LiveTimeRecommendations.spec.tsx +++ b/redisinsight/ui/src/components/live-time-recommendations/LiveTimeRecommendations.spec.tsx @@ -11,17 +11,18 @@ import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' import { Pages } from 'uiSrc/constants' import { RECOMMENDATIONS_DATA_MOCK } from 'uiSrc/mocks/handlers/recommendations/recommendationsHandler' import { appContextDbConfig, setRecommendationsShowHidden } from 'uiSrc/slices/app/context' -import _content from 'uiSrc/constants/dbAnalysisRecommendations.json' -import { IRecommendationsStatic } from 'uiSrc/slices/interfaces/recommendations' import { EXTERNAL_LINKS } from 'uiSrc/constants/links' +import { MOCK_RECOMMENDATIONS } from 'uiSrc/constants/mocks/mock-recommendations' import LiveTimeRecommendations from './LiveTimeRecommendations' -const recommendationsContent = _content as IRecommendationsStatic - let store: typeof mockedStore +const recommendationsContent = MOCK_RECOMMENDATIONS -const mockRecommendationsSelector = jest.requireActual('uiSrc/slices/recommendations/recommendations') +const mockRecommendationsSelector = { + ...jest.requireActual('uiSrc/slices/recommendations/recommendations'), + content: recommendationsContent, +} const mockAppContextDbConfigSelector = jest.requireActual('uiSrc/slices/app/context') jest.mock('uiSrc/slices/instances/instances', () => ({ @@ -190,7 +191,7 @@ describe('LiveTimeRecommendations', () => { data: { recommendations: [{ name: 'RTS' }], }, - isContentVisible: true + isContentVisible: true, })) const pushMock = jest.fn() reactRouterDom.useHistory = jest.fn().mockReturnValue({ push: pushMock }) @@ -223,7 +224,7 @@ describe('LiveTimeRecommendations', () => { data: { recommendations: [], }, - isContentVisible: true + isContentVisible: true, })) render() const afterRenderActions = [...store.getActions()] @@ -241,7 +242,7 @@ describe('LiveTimeRecommendations', () => { (recommendationsSelector as jest.Mock).mockImplementation(() => ({ ...mockRecommendationsSelector, isContentVisible: true, - data: RECOMMENDATIONS_DATA_MOCK + data: RECOMMENDATIONS_DATA_MOCK, })) const { queryByTestId } = render() @@ -266,7 +267,7 @@ describe('LiveTimeRecommendations', () => { (recommendationsSelector as jest.Mock).mockImplementation(() => ({ ...mockRecommendationsSelector, isContentVisible: true, - data: RECOMMENDATIONS_DATA_MOCK + data: RECOMMENDATIONS_DATA_MOCK, })) const { queryByTestId } = render() @@ -278,7 +279,7 @@ describe('LiveTimeRecommendations', () => { (recommendationsSelector as jest.Mock).mockImplementation(() => ({ ...mockRecommendationsSelector, isContentVisible: true, - data: RECOMMENDATIONS_DATA_MOCK + data: RECOMMENDATIONS_DATA_MOCK, })); (appContextDbConfig as jest.Mock).mockImplementation(() => ({ ...mockAppContextDbConfigSelector, @@ -294,7 +295,7 @@ describe('LiveTimeRecommendations', () => { (recommendationsSelector as jest.Mock).mockImplementation(() => ({ ...mockRecommendationsSelector, isContentVisible: true, - data: RECOMMENDATIONS_DATA_MOCK + data: RECOMMENDATIONS_DATA_MOCK, })); (appContextDbConfig as jest.Mock).mockImplementation(() => ({ ...mockAppContextDbConfigSelector, @@ -317,7 +318,7 @@ describe('LiveTimeRecommendations', () => { data: { ...RECOMMENDATIONS_DATA_MOCK, recommendations: RECOMMENDATIONS_DATA_MOCK.recommendations.map((rec) => ({ ...rec, hide: true })) - } + }, })); (appContextDbConfig as jest.Mock).mockImplementation(() => ({ ...mockAppContextDbConfigSelector, diff --git a/redisinsight/ui/src/components/live-time-recommendations/LiveTimeRecommendations.tsx b/redisinsight/ui/src/components/live-time-recommendations/LiveTimeRecommendations.tsx index 6f734b076f..5fc934a783 100644 --- a/redisinsight/ui/src/components/live-time-recommendations/LiveTimeRecommendations.tsx +++ b/redisinsight/ui/src/components/live-time-recommendations/LiveTimeRecommendations.tsx @@ -33,12 +33,11 @@ import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' import { workbenchGuidesSelector } from 'uiSrc/slices/workbench/wb-guides' import { workbenchTutorialsSelector } from 'uiSrc/slices/workbench/wb-tutorials' -import { IRecommendation, IRecommendationsStatic } from 'uiSrc/slices/interfaces/recommendations' +import { IRecommendation } from 'uiSrc/slices/interfaces/recommendations' import { appContextDbConfig, setRecommendationsShowHidden } from 'uiSrc/slices/app/context' import { ConnectionType } from 'uiSrc/slices/interfaces' import { createNewAnalysis } from 'uiSrc/slices/analytics/dbAnalysis' -import _content from 'uiSrc/constants/dbAnalysisRecommendations.json' import { ReactComponent as TriggerIcon } from 'uiSrc/assets/img/bulb.svg' import { ReactComponent as TriggerActiveIcon } from 'uiSrc/assets/img/bulb-active.svg' import InfoIcon from 'uiSrc/assets/img/icons/help_illus.svg' @@ -50,8 +49,6 @@ import WelcomeScreen from './components/welcome-screen' import PopoverRunAnalyze from './components/popover-run-analyze' import styles from './styles.module.scss' -const recommendationsContent = _content as IRecommendationsStatic - const DELAY_TO_SHOW_ONBOARDING_MS = 500 const LiveTimeRecommendations = () => { @@ -61,6 +58,7 @@ const LiveTimeRecommendations = () => { data: { recommendations, totalUnread }, isContentVisible, isHighlighted, + content: recommendationsContent, } = useSelector(recommendationsSelector) const { items: guides } = useSelector(workbenchGuidesSelector) const { items: tutorials } = useSelector(workbenchTutorialsSelector) @@ -198,6 +196,7 @@ const LiveTimeRecommendations = () => { tutorial={recommendationsContent[name]?.tutorial} provider={provider} params={params} + recommendationsContent={recommendationsContent} /> )) } diff --git a/redisinsight/ui/src/components/live-time-recommendations/components/recommendation/Recommendation.spec.tsx b/redisinsight/ui/src/components/live-time-recommendations/components/recommendation/Recommendation.spec.tsx index 1ba8e7516f..f75314aaa5 100644 --- a/redisinsight/ui/src/components/live-time-recommendations/components/recommendation/Recommendation.spec.tsx +++ b/redisinsight/ui/src/components/live-time-recommendations/components/recommendation/Recommendation.spec.tsx @@ -8,10 +8,17 @@ import { MOCK_GUIDES_ITEMS, MOCK_TUTORIALS_ITEMS, Pages } from 'uiSrc/constants' import { updateRecommendation } from 'uiSrc/slices/recommendations/recommendations' import { INSTANCE_ID_MOCK } from 'uiSrc/mocks/handlers/instances/instancesHandlers' +import { MOCK_RECOMMENDATIONS } from 'uiSrc/constants/mocks/mock-recommendations' import Recommendation, { IProps } from './Recommendation' +const recommendationsContent = MOCK_RECOMMENDATIONS const mockedProps = mock() +const instanceMock = { + ...instance(mockedProps), + recommendationsContent, +} + jest.mock('uiSrc/telemetry', () => ({ ...jest.requireActual('uiSrc/telemetry'), sendEventTelemetry: jest.fn(), @@ -28,12 +35,14 @@ const PROVIDER = 'RE_CLOUD' describe('Recommendation', () => { it('should render', () => { - expect(render()).toBeTruthy() + expect(render()).toBeTruthy() }) it('should render content if recommendation is not read', () => { render( { }) it('should render RecommendationVoting', () => { - const { container } = render() + const { container } = render() fireEvent.click(container.querySelector('[data-test-subj="searchJSON-button"]') as HTMLButtonElement) expect(screen.getByTestId('recommendation-voting')).toBeInTheDocument() }) @@ -56,7 +65,7 @@ describe('Recommendation', () => { const { container } = render( { const { container } = render( { const { container } = render( { it('should render hide/unhide button', () => { const name = 'searchJSON' - render() + render() expect(screen.getByTestId('toggle-hide-searchJSON-btn')).toBeInTheDocument() }) @@ -155,7 +164,7 @@ describe('Recommendation', () => { const nameMock = 'searchJSON' const { queryByTestId } = render( @@ -173,28 +182,28 @@ describe('Recommendation', () => { it('should not render "Tutorial" btn if tutorial is Undefined', () => { const name = 'searchJSON' - const { queryByTestId } = render() + const { queryByTestId } = render() expect(queryByTestId(`${name}-to-tutorial-btn`)).not.toBeInTheDocument() }) it('should render "Tutorial" if tutorial="path"', () => { const name = 'searchJSON' - const { queryByTestId } = render() + const { queryByTestId } = render() expect(queryByTestId(`${name}-to-tutorial-btn`)).toHaveTextContent('Tutorial') }) it('should render "Workbench" btn if tutorial=""', () => { const name = 'searchJSON' - const { queryByTestId } = render() + const { queryByTestId } = render() expect(queryByTestId(`${name}-to-tutorial-btn`)).toHaveTextContent('Workbench') }) it('should render Snooze button', () => { const name = 'searchJSON' - render() + render() expect(screen.getByTestId(`${name}-delete-btn`)).toBeInTheDocument() }) @@ -204,7 +213,7 @@ describe('Recommendation', () => { const nameMock = 'searchJSON' const { queryByTestId } = render( diff --git a/redisinsight/ui/src/components/live-time-recommendations/components/recommendation/Recommendation.tsx b/redisinsight/ui/src/components/live-time-recommendations/components/recommendation/Recommendation.tsx index eca9c54057..cc8be2b262 100644 --- a/redisinsight/ui/src/components/live-time-recommendations/components/recommendation/Recommendation.tsx +++ b/redisinsight/ui/src/components/live-time-recommendations/components/recommendation/Recommendation.tsx @@ -27,7 +27,6 @@ import { EXTERNAL_LINKS } from 'uiSrc/constants/links' import { IEnablementAreaItem } from 'uiSrc/slices/interfaces' import { IRecommendationsStatic, IRecommendationParams } from 'uiSrc/slices/interfaces/recommendations' -import _content from 'uiSrc/constants/dbAnalysisRecommendations.json' import RediStackDarkMin from 'uiSrc/assets/img/modules/redistack/RediStackDark-min.svg' import RediStackLightMin from 'uiSrc/assets/img/modules/redistack/RediStackLight-min.svg' import { ReactComponent as SnoozeIcon } from 'uiSrc/assets/img/icons/snooze.svg' @@ -46,10 +45,9 @@ export interface IProps { tutorial?: string provider?: string params: IRecommendationParams + recommendationsContent: IRecommendationsStatic } -const recommendationsContent = _content as IRecommendationsStatic - const Recommendation = ({ id, name, @@ -61,6 +59,7 @@ const Recommendation = ({ hide, provider, params, + recommendationsContent, }: IProps) => { const [isLoading, setIsLoading] = useState(false) const history = useHistory() diff --git a/redisinsight/ui/src/components/recommendation-voting/components/vote-option/VoteOption.tsx b/redisinsight/ui/src/components/recommendation-voting/components/vote-option/VoteOption.tsx index 12bccea002..3d08318336 100644 --- a/redisinsight/ui/src/components/recommendation-voting/components/vote-option/VoteOption.tsx +++ b/redisinsight/ui/src/components/recommendation-voting/components/vote-option/VoteOption.tsx @@ -15,10 +15,8 @@ import { import { EXTERNAL_LINKS } from 'uiSrc/constants/links' import { Vote } from 'uiSrc/constants/recommendations' import { putRecommendationVote } from 'uiSrc/slices/analytics/dbAnalysis' -import { IRecommendationsStatic } from 'uiSrc/slices/interfaces/recommendations' import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' -import _content from 'uiSrc/constants/dbAnalysisRecommendations.json' -import { updateLiveRecommendation } from 'uiSrc/slices/recommendations/recommendations' +import { recommendationsSelector, updateLiveRecommendation } from 'uiSrc/slices/recommendations/recommendations' import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances' import { Nullable } from 'uiSrc/utils' import PetardIcon from 'uiSrc/assets/img/icons/petard.svg' @@ -38,8 +36,6 @@ export interface Props { name: string } -const recommendationsContent = _content as IRecommendationsStatic - const VoteOption = (props: Props) => { const { voteOption, @@ -54,6 +50,7 @@ const VoteOption = (props: Props) => { const dispatch = useDispatch() const { id: instanceId = '', provider } = useSelector(connectedInstanceSelector) + const { content: recommendationsContent } = useSelector(recommendationsSelector) const onSuccessVoted = ({ vote, name }: { name: string, vote: Nullable }) => { sendEventTelemetry({ diff --git a/redisinsight/ui/src/constants/api.ts b/redisinsight/ui/src/constants/api.ts index e0c83ae3a6..0ae6f2b1f3 100644 --- a/redisinsight/ui/src/constants/api.ts +++ b/redisinsight/ui/src/constants/api.ts @@ -90,6 +90,7 @@ enum ApiEndpoints { PLUGINS = 'plugins', STATE = 'state', CONTENT_CREATE_DATABASE = 'static/content/create-redis.json', + CONTENT_RECOMMENDATIONS = 'static/content/recommendations.json', GUIDES_PATH = 'static/guides', TUTORIALS_PATH = 'static/tutorials', CUSTOM_TUTORIALS_PATH = 'static/custom-tutorials', diff --git a/redisinsight/ui/src/constants/dbAnalysisRecommendations.json b/redisinsight/ui/src/constants/dbAnalysisRecommendations.json deleted file mode 100644 index 6a4133f6e5..0000000000 --- a/redisinsight/ui/src/constants/dbAnalysisRecommendations.json +++ /dev/null @@ -1,1655 +0,0 @@ -{ - "luaScript": { - "id": "luaScript", - "title":"Avoid dynamic Lua script", - "content": [ - { - "type": "span", - "value": "Refrain from generating dynamic scripts, which can cause your Lua cache to grow and get out of control. Memory is consumed as scripts are loaded. If you have to use dynamic Lua scripts, then remember to track your Lua memory consumption and flush the cache periodically with a " - }, - { - "type": "code-link", - "value": { - "href": "https://redis.io/commands/script-flush/", - "name": "SCRIPT FLUSH" - } - }, - { - "type": "span", - "value": ". Also do not hardcode and/or programmatically generate key names in your Lua scripts because they do not work in a clustered Redis setup." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "span", - "value": "See the " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/interact/programmability/", - "name": "documentation" - } - }, - { - "type": "span", - "value": " to learn more about programmability in Redis." - } - ], - "badges": ["code_changes"] - }, - "useSmallerKeys": { - "id": "useSmallerKeys", - "title":"Use smaller keys", - "content": [ - { - "type": "paragraph", - "value": "Shorten key names to optimize memory usage. Though, in general, descriptive key names are always preferred, these large key names can eat a lot of the memory." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "span", - "value": "See the " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/management/optimization/memory-optimization/", - "name": "documentation" - } - }, - { - "type": "span", - "value": " for more memory optimization strategies." - } - ], - "badges": ["code_changes"] - }, - "bigHashes": { - "id": "bigHashes", - "telemetryEvent": "shardHashes", - "title": "Shard big hashes to small hashes", - "redisStack": true, - "tutorial": "/quick-guides/document/introduction.md", - "content": [ - { - "type": "paragraph", - "value": "If you have a hash with a large number of field-value pairs (> 5,000), and each pair is small enough - break it into smaller hashes to optimize memory usage. Additionally, try using smaller or shortened field names." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "paragraph", - "value": "Try the interactive tutorial to learn how to work with index and query documents modeled with hashes." - } - ], - "badges": ["code_changes", "configuration_changes"] - }, - "avoidLogicalDatabases": { - "id": "avoidLogicalDatabases", - "title": "Avoid using logical databases", - "content": [ - { - "type": "paragraph", - "value": "Using logical databases is an anti-pattern that Salvatore Sanfilippo, the creator of Redis, once called the worst design mistake he ever made in Redis." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "paragraph", - "value": "Although supported in Redis, logical databases are neither independent nor isolated in any other way and can freeze each other. Also, they are not supported by any clustering system (open source or Redis Enterprise clustering)." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "span", - "value": "If you need a multi-tenant environment, try " - }, - { - "type": "link", - "value": { - "href": "https://redis.com/try-free/", - "name": "Redis Cloud" - } - }, - { - "type": "span", - "value": ", where each tenant has its own Redis database endpoint which is completely isolated from the other Redis databases." - } - ], - "badges": ["code_changes"] - }, - "combineSmallStringsToHashes": { - "id": "combineSmallStringsToHashes", - "title": "Combine small strings to hashes", - "content": [ - { - "type": "span", - "value": "The strings data type has an overhead of about 90 bytes on a 64-bit machine, so if there is no need for different expiration values for these keys, combine small strings into a larger hash to optimize the memory usage. Also, ensure that the hash has less than " - }, - { - "type": "code", - "value": "hash-max-ziplist-entries" - }, - { - "type": "span", - "value": " elements and that the size of each element is within " - }, - { - "type": "code", - "value": "hash-max-ziplist-values" - }, - { - "type": "span", - "value": " bytes." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "paragraph", - "value": "Though this approach should not be used if you need different expiration values for string keys." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "span", - "value": "See the " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/management/optimization/memory-optimization/", - "name": "documentation" - } - }, - { - "type": "span", - "value": " for more memory optimization strategies." - } - ], - "badges": ["code_changes"] - }, - "increaseSetMaxIntsetEntries": { - "id": "increaseSetMaxIntsetEntries", - "title": "Increase the set-max-intset-entries", - "content": [ - { - "type": "paragraph", - "value": "Several set values with IntSet encoding exceed the set-max-intset-entries. Change the configuration in redis.conf to efficiently use the IntSet encoding. Though increasing this value will lead to an increase in the latency of set operations and CPU utilization." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "span", - "value": "Run " - }, - { - "type": "code", - "value": "INFO COMMANDSTATS" - }, - { - "type": "span", - "value": " before and after making this change to verify the latency numbers." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "span", - "value": "See the " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/management/optimization/memory-optimization/", - "name": "documentation" - } - }, - { - "type": "span", - "value": " for more memory optimization strategies." - } - ], - "badges": ["configuration_changes"] - }, - "hashHashtableToZiplist": { - "id": "hashHashtableToZiplist", - "title": "Convert hashtable to ziplist for hashes", - "content": [ - { - "type": "span", - "value": "Increase " - }, - { - "type": "code", - "value": "hash-max-ziplist-entries" - }, - { - "type": "span", - "value": " and/or " - }, - { - "type": "code", - "value": "hash-max-ziplist-values" - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "span", - "value": "If any value for a key exceeds " - }, - { - "type": "code", - "value": "hash-max-ziplist-entries" - }, - { - "type": "span", - "value": " or " - }, - { - "type": "code", - "value": "hash-max-ziplist-values" - }, - { - "type": "span", - "value": ", it is stored automatically as a Hashtable instead of a Ziplist, which consumes almost double the memory. So to save the memory, increase the configurations and convert your hashtables to ziplist. The trade-off can be an increase in latency and possibly an increase in CPU utilization." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "span", - "value": "See the " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/management/optimization/memory-optimization/", - "name": "documentation" - } - }, - { - "type": "span", - "value": " for more memory optimization strategies." - } - ], - "badges": ["configuration_changes"] - }, - "compressHashFieldNames": { - "id": "compressHashFieldNames", - "deprecated": true, - "title": "Compress Hash field names", - "content": [ - { - "type": "span", - "value": "Hash field name also consumes memory, so use smaller or shortened field names to reduce memory usage. " - }, - { - "type": "link", - "value": { - "href": "https://docs.redis.com/latest/ri/memory-optimizations/", - "name": "Read more" - } - } - ], - "badges": ["configuration_changes"] - }, - "compressionForList": { - "id": "compressionForList", - "title": "Enable compression for the list", - "content": [ - { - "type": "paragraph", - "value": "If you use long lists, and mostly access elements from the head and tail only, then you can enable compression." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "span", - "value": "Set " - }, - { - "type": "code", - "value": "list-compression-depth=1" - }, - { - "type": "span", - "value": " in redis.conf to compress every list node except the head and tail of the list. Though list operations that involve elements in the center of the list will get slower, the compression can increase CPU utilization." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "span", - "value": "Run " - }, - { - "type": "code", - "value": "INFO COMMANDSTATS" - }, - { - "type": "span", - "value": " before and after making this change to verify the latency numbers." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "span", - "value": "See the " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/management/optimization/memory-optimization/", - "name": "documentation" - } - }, - { - "type": "span", - "value": " for more memory optimization strategies." - } - ], - "badges": ["configuration_changes"] - }, - "bigStrings": { - "id": "bigStrings", - "title": "Avoid large strings", - "tutorial": "/quick-guides/document/introduction.md", - "content": [ - { - "type": "span", - "value": "If you are working with long strings you may need to retrieve parts or split them to handle in your application. Consider modeling your data using hashes or JSON. Both data structures provide fast and efficient data retrieval via the " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/interact/search-and-query/", - "name": "query and search capabilities" - } - }, - { - "type": "span", - "value": ", natively developed in Redis, and available as part of " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/about/about-stack/", - "name": "Redis Stack" - } - }, - { - "type": "span", - "value": ", " - }, - { - "type": "link", - "value": { - "href": "https://redis.com/redis-enterprise-cloud/overview/", - "name": "Redis Enterprise Cloud" - } - }, - { - "type": "span", - "value": " and " - }, - { - "type": "link", - "value": { - "href": "https://redis.com/redis-enterprise-software/overview/", - "name": "Redis Enterprise Software" - } - }, - { - "type": "span", - "value": "." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "paragraph", - "value": "Try the interactive tutorial to learn how to work with, index, and query documents modeled with JSON or hashes." - } - ], - "badges": ["configuration_changes"] - }, - "zSetHashtableToZiplist": { - "id": "zSetHashtableToZiplist", - "title": "Convert hashtable to ziplist for sorted sets", - "content": [ - { - "type": "span", - "value": "Increase " - }, - { - "type": "code", - "value": "zset-max-ziplist-entries" - }, - { - "type": "span", - "value": " and/or " - }, - { - "type": "code", - "value": "zset-max-ziplist-values" - }, - { - "type": "span", - "value": "." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "span", - "value": "If any value for a key exceeds " - }, - { - "type": "code", - "value": "zset-max-ziplist-entries" - }, - { - "type": "span", - "value": " or " - }, - { - "type": "code", - "value": "zset-max-ziplist-values" - }, - { - "type": "span", - "value": ", it is stored automatically as a Hashtable instead of a Ziplist, which consumes almost double the memory. So to save the memory, increase the configurations and convert your hashtables to ziplist. The trade-off can be an increase in latency and possibly an increase in CPU utilization." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "span", - "value": "See the " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/management/optimization/memory-optimization/", - "name": "documentation" - } - }, - { - "type": "span", - "value": " for more memory optimization strategies." - } - ], - "badges": ["configuration_changes"] - }, - "bigSets": { - "id": "bigSets", - "telemetryEvent": "optimizeExistenceChecks", - "title": "Consider using probabilistic data structures such as Bloom Filter or HyperLogLog", - "tutorial": "/quick-guides/probabilistic-data-structures/introduction.md", - "redisStack": true, - "content": [ - { - "type": "span", - "value": "If you are using sets for existence checks or duplicate elimination and can trade perfect accuracy for speed and memory efficiency, consider using the " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/data-types/probabilistic/bloom-filter/", - "name": "probabilistic data structures" - } - }, - { - "type": "span", - "value": ", especially useful for big data and streaming use cases." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "span", - "value": "These capabilities are part of " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/about/about-stack/", - "name": "Redis Stack" - } - }, - { - "type": "span", - "value": ", " - }, - { - "type": "link", - "value": { - "href": "https://redis.com/redis-enterprise-cloud/overview/", - "name": "Redis Enterprise Cloud" - } - }, - { - "type": "span", - "value": " and " - }, - { - "type": "link", - "value": { - "href": "https://redis.com/redis-enterprise-software/overview/", - "name": "Redis Enterprise Software" - } - }, - { - "type": "span", - "value": "." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "paragraph", - "value": "Try the interactive tutorial to learn more about the probabilistic data structures." - } - ], - "badges": ["configuration_changes"] - }, - "bigAmountOfConnectedClients": { - "id": "bigAmountOfConnectedClients", - "title": "Don't open a new connection for every request / every command", - "content": [ - { - "type": "span", - "value": "When the value of your " - }, - { - "type": "code", - "value": "total_connections_received" - }, - { - "type": "span", - "value": " in the stats section is high, it usually means that your application is opening and closing a connection for every request it makes. Opening a connection is an expensive operation that adds to both client and server latency. To rectify this, consult your " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/resources/clients/", - "name": "Redis client’s" - } - }, - { - "type": "span", - "value": " documentation and configure it to use persistent connections." - } - ], - "badges": ["code_changes"] - }, - "setPassword": { - "id": "setPassword", - "title": "Set a password", - "content": [ - { - "type": "span", - "value": "Protect your database by setting a password and using the " - }, - { - "type": "code-link", - "value": { - "href": "https://redis.io/commands/auth/", - "name": "AUTH" - } - }, - { - "type": "span", - "value": " command to authenticate the connection." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "span", - "value": "See the " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/management/security/", - "name": "documentation" - } - }, - { - "type": "span", - "value": " to learn more about Redis security." - } - ], - "badges": ["configuration_changes"] - }, - "RTS": { - "id": "RTS", - "telemetryEvent": "optimizeTimeSeries", - "title":"Try using the Redis native time series data structure and querying capabilities", - "redisStack": true, - "tutorial": "/quick-guides/time-series/introduction.md", - "content": [ - { - "type": "span", - "value": "If you are using sorted sets to store time series data, consider using the " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/data-types/timeseries/", - "name": "time series capabilities" - } - }, - { - "type": "span", - "value": "." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "paragraph", - "value": "Take advantage of advanced toolings such as downsampling and aggregation to ensure a small memory footprint without impacting performance." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "paragraph", - "value": "It also supports a flexible query language for visualization and monitoring, allows querying of different time series keys across the entire keyspace, and provides built-in connectors to popular tools." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "span", - "value": "The capabilities are part of " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/about/about-stack/", - "name": "Redis Stack" - } - }, - { - "type": "span", - "value": ", " - }, - { - "type": "link", - "value": { - "href": "https://redis.com/redis-enterprise-cloud/overview/", - "name": "Redis Enterprise Cloud" - } - }, - { - "type": "span", - "value": " and " - }, - { - "type": "link", - "value": { - "href": "https://redis.com/redis-enterprise-software/overview/", - "name": "Redis Enterprise Software" - } - }, - { - "type": "span", - "value": "." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "span", - "value": "Try the interactive tutorial to learn about the time series data structure and capabilities." - } - ], - "badges": ["configuration_changes"] - }, - "redisVersion": { - "id": "redisVersion", - "telemetryEvent": "updateDatabase", - "title":"Upgrade your Redis database to version 6 or above", - "redisStack": true, - "content": [ - { - "type": "paragraph", - "value": "Upgrade your database to version 6 or above to take advantage of the following:" - }, - { - "type": "list", - "value": [ - [ - { - "type": "link", - "value": { - "href": "https://redis.io/docs/management/security/acl/", - "name": "access control lists (ACLs)" - } - }, - { - "type": "span", - "value": " that let you create users with permissions for specific actions" - } - ], - [ - { - "type": "link", - "value": { - "href": "https://redis.io/docs/manual/client-side-caching/", - "name": "client-side caching" - } - }, - { - "type": "span", - "value": " for high-performance services" - } - ], - [ - { - "type": "span", - "value": "SSL support" - } - ], - [ - { - "type": "span", - "value": "optimized memory utilization through faster eviction of expired keys" - } - ] - ] - }, - { - "type": "span", - "value": "For a quick trial of the features, spin up a free developer database with " - }, - { - "type": "link", - "value": { - "href": "https://redis.com/try-free/", - "name": "Redis Cloud" - } - }, - { - "type": "span", - "value": " that also provides support for Redis Stack " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/interact/search-and-query/", - "name": "query and search" - } - }, - { - "type": "span", - "value": ", " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/data-types/json/", - "name": "JSON" - } - }, - { - "type": "span", - "value": ", " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/data-types/probabilistic/bloom-filter/", - "name": "probabilistic data structures" - } - }, - { - "type": "span", - "value": ", and " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/data-types/timeseries/", - "name": "Time Series" - } - }, - { - "type": "span", - "value": " capabilities." - } - ], - "badges": ["upgrade"] - }, - "redisSearch": { - "id": "redisSearch", - "title": "Optimize your query and search experience", - "deprecated": true, - "redisStack": true, - "tutorial": "/redis_stack/working_with_json.md", - "content": [ - { - "type": "link", - "value": { - "href": "https://redis.io/docs/interact/search-and-query/", - "name": "RediSearch" - } - }, - { - "type": "span", - "value": " was designed to help address your query needs and support a better development experience when dealing with complex data scenarios. Take a look at the " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/commands/?name=Ft", - "name": "powerful API options" - } - }, - { - "type": "span", - "value": " and try them. Supports full-text search, wildcards, fuzzy logic, and more." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "span", - "value": "Create a " - }, - { - "type": "link", - "value": { - "href": "https://redis.com/try-free/", - "name": "free Redis Stack database" - } - }, - { - "type": "span", - "value": " which extends the core capabilities of Redis OSS and uses modern data models and processing engines." - } - ], - "badges": ["upgrade"] - }, - "searchIndexes": { - "id": "searchIndexes", - "title":"Try using the indexing, querying, and full-text search, natively developed in Redis", - "redisStack": true, - "tutorial": "/quick-guides/document/introduction.md", - "content": [ - { - "type": "paragraph", - "value": "If you are using sorted sets for indexing, this may have its downsides:" - }, - { - "type": "list", - "value": [ - [ - { - "type": "span", - "value": "limited query flexibility such as filtering, sorting, or aggregations;" - } - ], - [ - { - "type": "span", - "value": "the risk of outdated or incorrect entries when other applications do not properly update or maintain the index;" - } - ], - [ - { - "type": "span", - "value": "difficulty in handling updates and deletions;" - } - ], - [ - { - "type": "span", - "value": "lack of horizontal scaling;" - } - ], - [ - { - "type": "span", - "value": "increased storage requirements to store both scores and members." - } - ] - ] - }, - { - "type": "span", - "value": "Consider using the " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/interact/search-and-query/", - "name": "query and search capabilities" - } - }, - { - "type": "span", - "value": ", natively developed in Redis, for performing text searches and complex structured queries. It offers real-time searching through synchronous indexing, ensuring read-your-writes consistency, without compromising the database performance." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "span", - "value": "The capability is part of " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/about/about-stack/", - "name": "Redis Stack" - } - }, - { - "type": "span", - "value": ", " - }, - { - "type": "link", - "value": { - "href": "https://redis.com/redis-enterprise-cloud/overview/", - "name": "Redis Enterprise Cloud" - } - }, - { - "type": "span", - "value": " and " - }, - { - "type": "link", - "value": { - "href": "https://redis.com/redis-enterprise-software/overview/", - "name": "Redis Enterprise Software" - } - }, - { - "type": "span", - "value": ". Also supported in an " - }, - { - "type": "link", - "value": { - "href": "https://docs.redis.com/latest/stack/search/search-active-active/", - "name": "Active-Active setup" - } - }, - { - "type": "span", - "value": "." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "paragraph", - "value": "Try the interactive tutorial to learn how to index and query documents modeled with JSON or hashes." - } - ], - "badges": ["upgrade"] - }, - "searchJSON": { - "id": "searchJSON", - "title": "Try indexing your JSON documents for efficient data retrieval", - "redisStack": true, - "tutorial": "/quick-guides/document/introduction.md", - "content": [ - { - "type": "span", - "value": "If you are working with JSON and need fast and efficient data retrieval, consider leveraging the " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/interact/search-and-query/", - "name": "query and search capabilities" - } - }, - { - "type": "span", - "value": ", natively developed in Redis." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "paragraph", - "value": "With it, you can perform complex structured queries, full-text searches, aggregations, geo-filtering, and much more. It offers real-time searching through synchronous indexing, ensuring read-your-writes consistency, without compromising the database performance." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "span", - "value": "The capabilities are part of " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/about/about-stack/", - "name": "Redis Stack" - } - }, - { - "type": "span", - "value": ", " - }, - { - "type": "link", - "value": { - "href": "https://redis.com/redis-enterprise-cloud/overview/", - "name": "Redis Enterprise Cloud" - } - }, - { - "type": "span", - "value": " and " - }, - { - "type": "link", - "value": { - "href": "https://redis.com/redis-enterprise-software/overview/", - "name": "Redis Enterprise Software" - } - }, - { - "type": "span", - "value": ". Also supported in an " - }, - { - "type": "link", - "value": { - "href": "https://docs.redis.com/latest/stack/search/search-active-active/", - "name": "Active-Active" - } - }, - { - "type": "span", - "value": " setup." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "paragraph", - "value": "Try the interactive tutorial to learn how to index and query documents modeled with JSON." - } - ] - }, - "stringToJson": { - "id": "stringToJson", - "title": "Try using our JSON native document store", - "redisStack": true, - "tutorial": "/quick-guides/document/introduction.md", - "content": [ - { - "type": "paragraph", - "value": "If you are using strings to store JSON documents, consider using the JSON capabilities." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "paragraph", - "value": "Primary features include:" - }, - { - "type": "list", - "value": [ - [ - { - "type": "span", - "value": "full support for the JSON standard" - } - ], - [ - { - "type": "span", - "value": "a " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/data-types/json/path/", - "name": "JSONPath" - } - }, - { - "type": "span", - "value": " syntax for selecting/updating elements inside documents" - } - ], - [ - { - "type": "span", - "value": "documents are stored as binary data in a tree structure, allowing fast access to sub-elements" - } - ], - [ - { - "type": "span", - "value": "typed atomic operations for all JSON value types" - } - ], - [ - { - "type": "span", - "value": "secondary indexing via the " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/interact/search-and-query/", - "name": "query and search capabilities" - } - } - ] - ] - }, - { - "type": "span", - "value": "All these capabilities are natively part of " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/about/about-stack/", - "name": "Redis Stack" - } - }, - { - "type": "span", - "value": ", " - }, - { - "type": "link", - "value": { - "href": "https://redis.com/redis-enterprise-cloud/overview/", - "name": "Redis Enterprise Cloud" - } - }, - { - "type": "span", - "value": " and " - }, - { - "type": "link", - "value": { - "href": "https://redis.com/redis-enterprise-software/overview/", - "name": "Redis Enterprise Software" - } - }, - { - "type": "span", - "value": "." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "paragraph", - "value": "Try the interactive tutorial to learn how to work with index and query documents modeled with JSON." - } - ] - }, - "searchVisualization": { - "id": "searchVisualization", - "title": "Try Workbench, the advanced command-line interface", - "tutorial": "", - "content": [ - { - "type": "paragraph", - "value": "Try RedisInsight Workbench, our advanced command-line interface with syntax highlighting, intelligent auto-complete, and the ability to work with commands in an editor mode." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "span", - "value": "It also provides user-friendly data visualizations and support for " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/about/about-stack/", - "name": "Redis Stack" - } - }, - { - "type": "span", - "value": " capabilities such as " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/data-types/json/", - "name": "JSON" - } - }, - { - "type": "span", - "value": ", " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/interact/search-and-query/", - "name": "query and search" - } - }, - { - "type": "span", - "value": ", and " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/data-types/timeseries/", - "name": "Time Series" - } - }, - { - "type": "span", - "value": "." - } - ] - }, - "searchHash": { - "id": "searchHash", - "title": "Try indexing your hash documents to query and retrieve data", - "tutorial": "/quick-guides/document/introduction.md", - "redisStack": true, - "content": [ - { - "type": "span", - "value": "If you are using hashes and would like to:" - }, - { - "type": "list", - "value": [ - [ - { - "type": "span", - "value": "query and retrieve data based on attributes other than the primary key;" - } - ], - [ - { - "type": "span", - "value": "sort and rank your data based on specific attributes;" - } - ], - [ - { - "type": "span", - "value": "perform complex structured aggregations transforming your data by grouping, sorting, and applying different functions (" - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/interact/search-and-query/search/aggregations/", - "name": "Apply Functions" - } - }, - { - "type": "span", - "value": ");" - } - ] - ] - }, - { - "type": "span", - "value": "consider using the " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/interact/search-and-query/", - "name": "query and search" - } - }, - { - "type": "span", - "value": " capabilities, natively developed in Redis. With it you can perform complex structured queries, full-text searches, aggregations, geo-filtering and much more." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "paragraph", - "value": "It offers real-time searching through synchronous indexing, ensuring read-your-writes consistency, without compromising the database performance." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "span", - "value": "The capability is part of " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/about/about-stack/", - "name": "Redis Stack" - } - }, - { - "type": "span", - "value": ", " - }, - { - "type": "link", - "value": { - "href": "https://redis.com/redis-enterprise-cloud/overview/", - "name": "Redis Enterprise Cloud" - } - }, - { - "type": "span", - "value": " and " - }, - { - "type": "link", - "value": { - "href": "https://redis.com/redis-enterprise-software/overview/", - "name": "Redis Enterprise Software" - } - }, - { - "type": "span", - "value": ". Also supported in an " - }, - { - "type": "link", - "value": { - "href": "https://docs.redis.com/latest/stack/search/search-active-active/", - "name": "Active-Active setup" - } - }, - { - "type": "span", - "value": "." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "span", - "value": "Try the interactive tutorial to learn how to work with index and query documents modeled with JSON." - } - ], - "badges": ["code_changes", "configuration_changes"] - }, - "luaToFunctions": { - "id": "luaToFunctions", - "title": "Consider using triggers and functions", - "tutorial": "/quick-guides/triggers-and-functions/introduction.md", - "content": [ - { - "type": "paragraph", - "value": "If you are using LUA scripts to run application logic inside Redis, consider using triggers and functions to take advantage of Javascript's vast ecosystem of libraries and frameworks and modern, expressive syntax." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "paragraph", - "value": "Triggers and functions can execute business logic on changes within a database, and read across all shards in clustered databases." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "span", - "value": "These capabilities are part of " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/about/about-stack/", - "name": "Redis Stack" - } - }, - { - "type": "span", - "value": ", " - }, - { - "type": "link", - "value": { - "href": "https://redis.com/redis-enterprise-cloud/overview/", - "name": "Redis Enterprise Cloud" - } - }, - { - "type": "span", - "value": " and " - }, - { - "type": "link", - "value": { - "href": "https://redis.com/redis-enterprise-software/overview/", - "name": "Redis Enterprise Software" - } - }, - { - "type": "span", - "value": "." - } - ], - "badges": ["code_changes"] - }, - "functionsWithStreams": { - "id": "functionsWithStreams", - "title": "Consider using triggers and functions to react in real-time to stream entries", - "tutorial": "/quick-guides/triggers-and-functions/introduction.md", - "content": [ - { - "type": "paragraph", - "value": "If you need to manipulate your data based on Redis stream entries, consider using stream triggers that are a part of triggers and functions. It can help lower latency by moving business logic closer to the data." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "paragraph", - "value": "Try triggers and functions to take advantage of Javascript's vast ecosystem of libraries and frameworks and modern, expressive syntax." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "paragraph", - "value": "These capabilities can execute business logic on changes within a database, and read across all shards in clustered databases." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "span", - "value": "Triggers and functions are part of " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/about/about-stack/", - "name": "Redis Stack" - } - }, - { - "type": "span", - "value": ", " - }, - { - "type": "link", - "value": { - "href": "https://redis.com/redis-enterprise-cloud/overview/", - "name": "Redis Enterprise Cloud" - } - }, - { - "type": "span", - "value": " and " - }, - { - "type": "link", - "value": { - "href": "https://redis.com/redis-enterprise-software/overview/", - "name": "Redis Enterprise Software" - } - }, - { - "type": "span", - "value": "." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "paragraph", - "value": "Try the interactive tutorial to learn more about triggers and functions." - } - ], - "badges": ["code_changes"] - }, - "functionsWithKeyspace": { - "id": "functionsWithKeyspace", - "title": "Consider using triggers and functions to react in real-time to database changes", - "tutorial": "/quick-guides/triggers-and-functions/introduction.md", - "content": [ - { - "type": "paragraph", - "value": "If you need to manipulate your data based on keyspace notifications, consider using keyspace triggers that are a part of triggers and functions. It can help lower latency by moving business logic closer to the data." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "paragraph", - "value": "Try triggers and functions to take advantage of Javascript's vast ecosystem of libraries and frameworks and modern, expressive syntax." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "paragraph", - "value": "These capabilities can execute business logic on changes within a database, and read across all shards in clustered databases." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "span", - "value": "Triggers and functions are part of " - }, - { - "type": "link", - "value": { - "href": "https://redis.io/docs/about/about-stack/", - "name": "Redis Stack" - } - }, - { - "type": "span", - "value": ", " - }, - { - "type": "link", - "value": { - "href": "https://redis.com/redis-enterprise-cloud/overview/", - "name": "Redis Enterprise Cloud" - } - }, - { - "type": "span", - "value": " and " - }, - { - "type": "link", - "value": { - "href": "https://redis.com/redis-enterprise-software/overview/", - "name": "Redis Enterprise Software" - } - }, - { - "type": "span", - "value": "." - }, - { - "type": "spacer", - "value": "l" - }, - { - "type": "paragraph", - "value": "Try the interactive tutorial to learn more about triggers and functions." - } - ], - "badges": ["code_changes"] - } -} diff --git a/redisinsight/ui/src/constants/mocks/mock-recommendations.ts b/redisinsight/ui/src/constants/mocks/mock-recommendations.ts new file mode 100644 index 0000000000..08ed30a555 --- /dev/null +++ b/redisinsight/ui/src/constants/mocks/mock-recommendations.ts @@ -0,0 +1,1657 @@ +import { IRecommendationsStatic } from 'uiSrc/slices/interfaces/recommendations' + +export const MOCK_RECOMMENDATIONS: IRecommendationsStatic = { + luaScript: { + id: 'luaScript', + title: 'Avoid dynamic Lua script', + content: [ + { + type: 'span', + value: 'Refrain from generating dynamic scripts, which can cause your Lua cache to grow and get out of control. Memory is consumed as scripts are loaded. If you have to use dynamic Lua scripts, then remember to track your Lua memory consumption and flush the cache periodically with a ' + }, + { + type: 'code-link', + value: { + href: 'https://redis.io/commands/script-flush/', + name: 'SCRIPT FLUSH' + } + }, + { + type: 'span', + value: '. Also do not hardcode and/or programmatically generate key names in your Lua scripts because they do not work in a clustered Redis setup.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'span', + value: 'See the ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/interact/programmability/', + name: 'documentation' + } + }, + { + type: 'span', + value: ' to learn more about programmability in Redis.' + } + ], + badges: ['code_changes'] + }, + useSmallerKeys: { + id: 'useSmallerKeys', + title: 'Use smaller keys', + content: [ + { + type: 'paragraph', + value: 'Shorten key names to optimize memory usage. Though, in general, descriptive key names are always preferred, these large key names can eat a lot of the memory.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'span', + value: 'See the ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/management/optimization/memory-optimization/', + name: 'documentation' + } + }, + { + type: 'span', + value: ' for more memory optimization strategies.' + } + ], + badges: ['code_changes'] + }, + bigHashes: { + id: 'bigHashes', + telemetryEvent: 'shardHashes', + title: 'Shard big hashes to small hashes', + redisStack: true, + tutorial: '/quick-guides/document/introduction.md', + content: [ + { + type: 'paragraph', + value: 'If you have a hash with a large number of field-value pairs (> 5,000), and each pair is small enough - break it into smaller hashes to optimize memory usage. Additionally, try using smaller or shortened field names.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'paragraph', + value: 'Try the interactive tutorial to learn how to work with index and query documents modeled with hashes.' + } + ], + badges: ['code_changes', 'configuration_changes'] + }, + avoidLogicalDatabases: { + id: 'avoidLogicalDatabases', + title: 'Avoid using logical databases', + content: [ + { + type: 'paragraph', + value: 'Using logical databases is an anti-pattern that Salvatore Sanfilippo, the creator of Redis, once called the worst design mistake he ever made in Redis.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'paragraph', + value: 'Although supported in Redis, logical databases are neither independent nor isolated in any other way and can freeze each other. Also, they are not supported by any clustering system (open source or Redis Enterprise clustering).' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'span', + value: 'If you need a multi-tenant environment, try ' + }, + { + type: 'link', + value: { + href: 'https://redis.com/try-free/', + name: 'Redis Cloud' + } + }, + { + type: 'span', + value: ', where each tenant has its own Redis database endpoint which is completely isolated from the other Redis databases.' + } + ], + badges: ['code_changes'] + }, + combineSmallStringsToHashes: { + id: 'combineSmallStringsToHashes', + title: 'Combine small strings to hashes', + content: [ + { + type: 'span', + value: 'The strings data type has an overhead of about 90 bytes on a 64-bit machine, so if there is no need for different expiration values for these keys, combine small strings into a larger hash to optimize the memory usage. Also, ensure that the hash has less than ' + }, + { + type: 'code', + value: 'hash-max-ziplist-entries' + }, + { + type: 'span', + value: ' elements and that the size of each element is within ' + }, + { + type: 'code', + value: 'hash-max-ziplist-values' + }, + { + type: 'span', + value: ' bytes.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'paragraph', + value: 'Though this approach should not be used if you need different expiration values for string keys.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'span', + value: 'See the ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/management/optimization/memory-optimization/', + name: 'documentation' + } + }, + { + type: 'span', + value: ' for more memory optimization strategies.' + } + ], + badges: ['code_changes'] + }, + increaseSetMaxIntsetEntries: { + id: 'increaseSetMaxIntsetEntries', + title: 'Increase the set-max-intset-entries', + content: [ + { + type: 'paragraph', + value: 'Several set values with IntSet encoding exceed the set-max-intset-entries. Change the configuration in redis.conf to efficiently use the IntSet encoding. Though increasing this value will lead to an increase in the latency of set operations and CPU utilization.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'span', + value: 'Run ' + }, + { + type: 'code', + value: 'INFO COMMANDSTATS' + }, + { + type: 'span', + value: ' before and after making this change to verify the latency numbers.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'span', + value: 'See the ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/management/optimization/memory-optimization/', + name: 'documentation' + } + }, + { + type: 'span', + value: ' for more memory optimization strategies.' + } + ], + badges: ['configuration_changes'] + }, + hashHashtableToZiplist: { + id: 'hashHashtableToZiplist', + title: 'Convert hashtable to ziplist for hashes', + content: [ + { + type: 'span', + value: 'Increase ' + }, + { + type: 'code', + value: 'hash-max-ziplist-entries' + }, + { + type: 'span', + value: ' and/or ' + }, + { + type: 'code', + value: 'hash-max-ziplist-values' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'span', + value: 'If any value for a key exceeds ' + }, + { + type: 'code', + value: 'hash-max-ziplist-entries' + }, + { + type: 'span', + value: ' or ' + }, + { + type: 'code', + value: 'hash-max-ziplist-values' + }, + { + type: 'span', + value: ', it is stored automatically as a Hashtable instead of a Ziplist, which consumes almost double the memory. So to save the memory, increase the configurations and convert your hashtables to ziplist. The trade-off can be an increase in latency and possibly an increase in CPU utilization.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'span', + value: 'See the ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/management/optimization/memory-optimization/', + name: 'documentation' + } + }, + { + type: 'span', + value: ' for more memory optimization strategies.' + } + ], + badges: ['configuration_changes'] + }, + compressHashFieldNames: { + id: 'compressHashFieldNames', + deprecated: true, + title: 'Compress Hash field names', + content: [ + { + type: 'span', + value: 'Hash field name also consumes memory, so use smaller or shortened field names to reduce memory usage. ' + }, + { + type: 'link', + value: { + href: 'https://docs.redis.com/latest/ri/memory-optimizations/', + name: 'Read more' + } + } + ], + badges: ['configuration_changes'] + }, + compressionForList: { + id: 'compressionForList', + title: 'Enable compression for the list', + content: [ + { + type: 'paragraph', + value: 'If you use long lists, and mostly access elements from the head and tail only, then you can enable compression.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'span', + value: 'Set ' + }, + { + type: 'code', + value: 'list-compression-depth=1' + }, + { + type: 'span', + value: ' in redis.conf to compress every list node except the head and tail of the list. Though list operations that involve elements in the center of the list will get slower, the compression can increase CPU utilization.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'span', + value: 'Run ' + }, + { + type: 'code', + value: 'INFO COMMANDSTATS' + }, + { + type: 'span', + value: ' before and after making this change to verify the latency numbers.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'span', + value: 'See the ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/management/optimization/memory-optimization/', + name: 'documentation' + } + }, + { + type: 'span', + value: ' for more memory optimization strategies.' + } + ], + badges: ['configuration_changes'] + }, + bigStrings: { + id: 'bigStrings', + title: 'Avoid large strings', + tutorial: '/quick-guides/document/introduction.md', + content: [ + { + type: 'span', + value: 'If you are working with long strings you may need to retrieve parts or split them to handle in your application. Consider modeling your data using hashes or JSON. Both data structures provide fast and efficient data retrieval via the ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/interact/search-and-query/', + name: 'query and search capabilities' + } + }, + { + type: 'span', + value: ', natively developed in Redis, and available as part of ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/about/about-stack/', + name: 'Redis Stack' + } + }, + { + type: 'span', + value: ', ' + }, + { + type: 'link', + value: { + href: 'https://redis.com/redis-enterprise-cloud/overview/', + name: 'Redis Enterprise Cloud' + } + }, + { + type: 'span', + value: ' and ' + }, + { + type: 'link', + value: { + href: 'https://redis.com/redis-enterprise-software/overview/', + name: 'Redis Enterprise Software' + } + }, + { + type: 'span', + value: '.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'paragraph', + value: 'Try the interactive tutorial to learn how to work with, index, and query documents modeled with JSON or hashes.' + } + ], + badges: ['configuration_changes'] + }, + zSetHashtableToZiplist: { + id: 'zSetHashtableToZiplist', + title: 'Convert hashtable to ziplist for sorted sets', + content: [ + { + type: 'span', + value: 'Increase ' + }, + { + type: 'code', + value: 'zset-max-ziplist-entries' + }, + { + type: 'span', + value: ' and/or ' + }, + { + type: 'code', + value: 'zset-max-ziplist-values' + }, + { + type: 'span', + value: '.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'span', + value: 'If any value for a key exceeds ' + }, + { + type: 'code', + value: 'zset-max-ziplist-entries' + }, + { + type: 'span', + value: ' or ' + }, + { + type: 'code', + value: 'zset-max-ziplist-values' + }, + { + type: 'span', + value: ', it is stored automatically as a Hashtable instead of a Ziplist, which consumes almost double the memory. So to save the memory, increase the configurations and convert your hashtables to ziplist. The trade-off can be an increase in latency and possibly an increase in CPU utilization.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'span', + value: 'See the ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/management/optimization/memory-optimization/', + name: 'documentation' + } + }, + { + type: 'span', + value: ' for more memory optimization strategies.' + } + ], + badges: ['configuration_changes'] + }, + bigSets: { + id: 'bigSets', + telemetryEvent: 'optimizeExistenceChecks', + title: 'Consider using probabilistic data structures such as Bloom Filter or HyperLogLog', + tutorial: '/quick-guides/probabilistic-data-structures/introduction.md', + redisStack: true, + content: [ + { + type: 'span', + value: 'If you are using sets for existence checks or duplicate elimination and can trade perfect accuracy for speed and memory efficiency, consider using the ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/data-types/probabilistic/bloom-filter/', + name: 'probabilistic data structures' + } + }, + { + type: 'span', + value: ', especially useful for big data and streaming use cases.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'span', + value: 'These capabilities are part of ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/about/about-stack/', + name: 'Redis Stack' + } + }, + { + type: 'span', + value: ', ' + }, + { + type: 'link', + value: { + href: 'https://redis.com/redis-enterprise-cloud/overview/', + name: 'Redis Enterprise Cloud' + } + }, + { + type: 'span', + value: ' and ' + }, + { + type: 'link', + value: { + href: 'https://redis.com/redis-enterprise-software/overview/', + name: 'Redis Enterprise Software' + } + }, + { + type: 'span', + value: '.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'paragraph', + value: 'Try the interactive tutorial to learn more about the probabilistic data structures.' + } + ], + badges: ['configuration_changes'] + }, + bigAmountOfConnectedClients: { + id: 'bigAmountOfConnectedClients', + title: "Don't open a new connection for every request / every command", + content: [ + { + type: 'span', + value: 'When the value of your ' + }, + { + type: 'code', + value: 'total_connections_received' + }, + { + type: 'span', + value: ' in the stats section is high, it usually means that your application is opening and closing a connection for every request it makes. Opening a connection is an expensive operation that adds to both client and server latency. To rectify this, consult your ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/resources/clients/', + name: 'Redis client’s' + } + }, + { + type: 'span', + value: ' documentation and configure it to use persistent connections.' + } + ], + badges: ['code_changes'] + }, + setPassword: { + id: 'setPassword', + title: 'Set a password', + content: [ + { + type: 'span', + value: 'Protect your database by setting a password and using the ' + }, + { + type: 'code-link', + value: { + href: 'https://redis.io/commands/auth/', + name: 'AUTH' + } + }, + { + type: 'span', + value: ' command to authenticate the connection.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'span', + value: 'See the ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/management/security/', + name: 'documentation' + } + }, + { + type: 'span', + value: ' to learn more about Redis security.' + } + ], + badges: ['configuration_changes'] + }, + RTS: { + id: 'RTS', + telemetryEvent: 'optimizeTimeSeries', + title: 'Try using the Redis native time series data structure and querying capabilities', + redisStack: true, + tutorial: '/quick-guides/time-series/introduction.md', + content: [ + { + type: 'span', + value: 'If you are using sorted sets to store time series data, consider using the ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/data-types/timeseries/', + name: 'time series capabilities' + } + }, + { + type: 'span', + value: '.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'paragraph', + value: 'Take advantage of advanced toolings such as downsampling and aggregation to ensure a small memory footprint without impacting performance.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'paragraph', + value: 'It also supports a flexible query language for visualization and monitoring, allows querying of different time series keys across the entire keyspace, and provides built-in connectors to popular tools.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'span', + value: 'The capabilities are part of ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/about/about-stack/', + name: 'Redis Stack' + } + }, + { + type: 'span', + value: ', ' + }, + { + type: 'link', + value: { + href: 'https://redis.com/redis-enterprise-cloud/overview/', + name: 'Redis Enterprise Cloud' + } + }, + { + type: 'span', + value: ' and ' + }, + { + type: 'link', + value: { + href: 'https://redis.com/redis-enterprise-software/overview/', + name: 'Redis Enterprise Software' + } + }, + { + type: 'span', + value: '.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'span', + value: 'Try the interactive tutorial to learn about the time series data structure and capabilities.' + } + ], + badges: ['configuration_changes'] + }, + redisVersion: { + id: 'redisVersion', + telemetryEvent: 'updateDatabase', + title: 'Upgrade your Redis database to version 6 or above', + redisStack: true, + content: [ + { + type: 'paragraph', + value: 'Upgrade your database to version 6 or above to take advantage of the following:' + }, + { + type: 'list', + value: [ + [ + { + type: 'link', + value: { + href: 'https://redis.io/docs/management/security/acl/', + name: 'access control lists (ACLs)' + } + }, + { + type: 'span', + value: ' that let you create users with permissions for specific actions' + } + ], + [ + { + type: 'link', + value: { + href: 'https://redis.io/docs/manual/client-side-caching/', + name: 'client-side caching' + } + }, + { + type: 'span', + value: ' for high-performance services' + } + ], + [ + { + type: 'span', + value: 'SSL support' + } + ], + [ + { + type: 'span', + value: 'optimized memory utilization through faster eviction of expired keys' + } + ] + ] + }, + { + type: 'span', + value: 'For a quick trial of the features, spin up a free developer database with ' + }, + { + type: 'link', + value: { + href: 'https://redis.com/try-free/', + name: 'Redis Cloud' + } + }, + { + type: 'span', + value: ' that also provides support for Redis Stack ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/interact/search-and-query/', + name: 'query and search' + } + }, + { + type: 'span', + value: ', ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/data-types/json/', + name: 'JSON' + } + }, + { + type: 'span', + value: ', ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/data-types/probabilistic/bloom-filter/', + name: 'probabilistic data structures' + } + }, + { + type: 'span', + value: ', and ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/data-types/timeseries/', + name: 'Time Series' + } + }, + { + type: 'span', + value: ' capabilities.' + } + ], + badges: ['upgrade'] + }, + redisSearch: { + id: 'redisSearch', + title: 'Optimize your query and search experience', + deprecated: true, + redisStack: true, + tutorial: '/redis_stack/working_with_json.md', + content: [ + { + type: 'link', + value: { + href: 'https://redis.io/docs/interact/search-and-query/', + name: 'RediSearch' + } + }, + { + type: 'span', + value: ' was designed to help address your query needs and support a better development experience when dealing with complex data scenarios. Take a look at the ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/commands/?name=Ft', + name: 'powerful API options' + } + }, + { + type: 'span', + value: ' and try them. Supports full-text search, wildcards, fuzzy logic, and more.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'span', + value: 'Create a ' + }, + { + type: 'link', + value: { + href: 'https://redis.com/try-free/', + name: 'free Redis Stack database' + } + }, + { + type: 'span', + value: ' which extends the core capabilities of Redis OSS and uses modern data models and processing engines.' + } + ], + badges: ['upgrade'] + }, + searchIndexes: { + id: 'searchIndexes', + title: 'Try using the indexing, querying, and full-text search, natively developed in Redis', + redisStack: true, + tutorial: '/quick-guides/document/introduction.md', + content: [ + { + type: 'paragraph', + value: 'If you are using sorted sets for indexing, this may have its downsides:' + }, + { + type: 'list', + value: [ + [ + { + type: 'span', + value: 'limited query flexibility such as filtering, sorting, or aggregations;' + } + ], + [ + { + type: 'span', + value: 'the risk of outdated or incorrect entries when other applications do not properly update or maintain the index;' + } + ], + [ + { + type: 'span', + value: 'difficulty in handling updates and deletions;' + } + ], + [ + { + type: 'span', + value: 'lack of horizontal scaling;' + } + ], + [ + { + type: 'span', + value: 'increased storage requirements to store both scores and members.' + } + ] + ] + }, + { + type: 'span', + value: 'Consider using the ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/interact/search-and-query/', + name: 'query and search capabilities' + } + }, + { + type: 'span', + value: ', natively developed in Redis, for performing text searches and complex structured queries. It offers real-time searching through synchronous indexing, ensuring read-your-writes consistency, without compromising the database performance.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'span', + value: 'The capability is part of ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/about/about-stack/', + name: 'Redis Stack' + } + }, + { + type: 'span', + value: ', ' + }, + { + type: 'link', + value: { + href: 'https://redis.com/redis-enterprise-cloud/overview/', + name: 'Redis Enterprise Cloud' + } + }, + { + type: 'span', + value: ' and ' + }, + { + type: 'link', + value: { + href: 'https://redis.com/redis-enterprise-software/overview/', + name: 'Redis Enterprise Software' + } + }, + { + type: 'span', + value: '. Also supported in an ' + }, + { + type: 'link', + value: { + href: 'https://docs.redis.com/latest/stack/search/search-active-active/', + name: 'Active-Active setup' + } + }, + { + type: 'span', + value: '.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'paragraph', + value: 'Try the interactive tutorial to learn how to index and query documents modeled with JSON or hashes.' + } + ], + badges: ['upgrade'] + }, + searchJSON: { + id: 'searchJSON', + title: 'Try indexing your JSON documents for efficient data retrieval', + redisStack: true, + tutorial: '/quick-guides/document/introduction.md', + content: [ + { + type: 'span', + value: 'If you are working with JSON and need fast and efficient data retrieval, consider leveraging the ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/interact/search-and-query/', + name: 'query and search capabilities' + } + }, + { + type: 'span', + value: ', natively developed in Redis.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'paragraph', + value: 'With it, you can perform complex structured queries, full-text searches, aggregations, geo-filtering, and much more. It offers real-time searching through synchronous indexing, ensuring read-your-writes consistency, without compromising the database performance.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'span', + value: 'The capabilities are part of ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/about/about-stack/', + name: 'Redis Stack' + } + }, + { + type: 'span', + value: ', ' + }, + { + type: 'link', + value: { + href: 'https://redis.com/redis-enterprise-cloud/overview/', + name: 'Redis Enterprise Cloud' + } + }, + { + type: 'span', + value: ' and ' + }, + { + type: 'link', + value: { + href: 'https://redis.com/redis-enterprise-software/overview/', + name: 'Redis Enterprise Software' + } + }, + { + type: 'span', + value: '. Also supported in an ' + }, + { + type: 'link', + value: { + href: 'https://docs.redis.com/latest/stack/search/search-active-active/', + name: 'Active-Active' + } + }, + { + type: 'span', + value: ' setup.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'paragraph', + value: 'Try the interactive tutorial to learn how to index and query documents modeled with JSON.' + } + ] + }, + stringToJson: { + id: 'stringToJson', + title: 'Try using our JSON native document store', + redisStack: true, + tutorial: '/quick-guides/document/introduction.md', + content: [ + { + type: 'paragraph', + value: 'If you are using strings to store JSON documents, consider using the JSON capabilities.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'paragraph', + value: 'Primary features include:' + }, + { + type: 'list', + value: [ + [ + { + type: 'span', + value: 'full support for the JSON standard' + } + ], + [ + { + type: 'span', + value: 'a ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/data-types/json/path/', + name: 'JSONPath' + } + }, + { + type: 'span', + value: ' syntax for selecting/updating elements inside documents' + } + ], + [ + { + type: 'span', + value: 'documents are stored as binary data in a tree structure, allowing fast access to sub-elements' + } + ], + [ + { + type: 'span', + value: 'typed atomic operations for all JSON value types' + } + ], + [ + { + type: 'span', + value: 'secondary indexing via the ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/interact/search-and-query/', + name: 'query and search capabilities' + } + } + ] + ] + }, + { + type: 'span', + value: 'All these capabilities are natively part of ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/about/about-stack/', + name: 'Redis Stack' + } + }, + { + type: 'span', + value: ', ' + }, + { + type: 'link', + value: { + href: 'https://redis.com/redis-enterprise-cloud/overview/', + name: 'Redis Enterprise Cloud' + } + }, + { + type: 'span', + value: ' and ' + }, + { + type: 'link', + value: { + href: 'https://redis.com/redis-enterprise-software/overview/', + name: 'Redis Enterprise Software' + } + }, + { + type: 'span', + value: '.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'paragraph', + value: 'Try the interactive tutorial to learn how to work with index and query documents modeled with JSON.' + } + ] + }, + searchVisualization: { + id: 'searchVisualization', + title: 'Try Workbench, the advanced command-line interface', + tutorial: '', + content: [ + { + type: 'paragraph', + value: 'Try RedisInsight Workbench, our advanced command-line interface with syntax highlighting, intelligent auto-complete, and the ability to work with commands in an editor mode.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'span', + value: 'It also provides user-friendly data visualizations and support for ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/about/about-stack/', + name: 'Redis Stack' + } + }, + { + type: 'span', + value: ' capabilities such as ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/data-types/json/', + name: 'JSON' + } + }, + { + type: 'span', + value: ', ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/interact/search-and-query/', + name: 'query and search' + } + }, + { + type: 'span', + value: ', and ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/data-types/timeseries/', + name: 'Time Series' + } + }, + { + type: 'span', + value: '.' + } + ] + }, + searchHash: { + id: 'searchHash', + title: 'Try indexing your hash documents to query and retrieve data', + tutorial: '/quick-guides/document/introduction.md', + redisStack: true, + content: [ + { + type: 'span', + value: 'If you are using hashes and would like to:' + }, + { + type: 'list', + value: [ + [ + { + type: 'span', + value: 'query and retrieve data based on attributes other than the primary key;' + } + ], + [ + { + type: 'span', + value: 'sort and rank your data based on specific attributes;' + } + ], + [ + { + type: 'span', + value: 'perform complex structured aggregations transforming your data by grouping, sorting, and applying different functions (' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/interact/search-and-query/search/aggregations/', + name: 'Apply Functions' + } + }, + { + type: 'span', + value: ');' + } + ] + ] + }, + { + type: 'span', + value: 'consider using the ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/interact/search-and-query/', + name: 'query and search' + } + }, + { + type: 'span', + value: ' capabilities, natively developed in Redis. With it you can perform complex structured queries, full-text searches, aggregations, geo-filtering and much more.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'paragraph', + value: 'It offers real-time searching through synchronous indexing, ensuring read-your-writes consistency, without compromising the database performance.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'span', + value: 'The capability is part of ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/about/about-stack/', + name: 'Redis Stack' + } + }, + { + type: 'span', + value: ', ' + }, + { + type: 'link', + value: { + href: 'https://redis.com/redis-enterprise-cloud/overview/', + name: 'Redis Enterprise Cloud' + } + }, + { + type: 'span', + value: ' and ' + }, + { + type: 'link', + value: { + href: 'https://redis.com/redis-enterprise-software/overview/', + name: 'Redis Enterprise Software' + } + }, + { + type: 'span', + value: '. Also supported in an ' + }, + { + type: 'link', + value: { + href: 'https://docs.redis.com/latest/stack/search/search-active-active/', + name: 'Active-Active setup' + } + }, + { + type: 'span', + value: '.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'span', + value: 'Try the interactive tutorial to learn how to work with index and query documents modeled with JSON.' + } + ], + badges: ['code_changes', 'configuration_changes'] + }, + luaToFunctions: { + id: 'luaToFunctions', + title: 'Consider using triggers and functions', + tutorial: '/quick-guides/triggers-and-functions/introduction.md', + content: [ + { + type: 'paragraph', + value: "If you are using LUA scripts to run application logic inside Redis, consider using triggers and functions to take advantage of Javascript's vast ecosystem of libraries and frameworks and modern, expressive syntax." + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'paragraph', + value: 'Triggers and functions can execute business logic on changes within a database, and read across all shards in clustered databases.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'span', + value: 'These capabilities are part of ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/about/about-stack/', + name: 'Redis Stack' + } + }, + { + type: 'span', + value: ', ' + }, + { + type: 'link', + value: { + href: 'https://redis.com/redis-enterprise-cloud/overview/', + name: 'Redis Enterprise Cloud' + } + }, + { + type: 'span', + value: ' and ' + }, + { + type: 'link', + value: { + href: 'https://redis.com/redis-enterprise-software/overview/', + name: 'Redis Enterprise Software' + } + }, + { + type: 'span', + value: '.' + } + ], + badges: ['code_changes'] + }, + functionsWithStreams: { + id: 'functionsWithStreams', + title: 'Consider using triggers and functions to react in real-time to stream entries', + tutorial: '/quick-guides/triggers-and-functions/introduction.md', + content: [ + { + type: 'paragraph', + value: 'If you need to manipulate your data based on Redis stream entries, consider using stream triggers that are a part of triggers and functions. It can help lower latency by moving business logic closer to the data.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'paragraph', + value: "Try triggers and functions to take advantage of Javascript's vast ecosystem of libraries and frameworks and modern, expressive syntax." + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'paragraph', + value: 'These capabilities can execute business logic on changes within a database, and read across all shards in clustered databases.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'span', + value: 'Triggers and functions are part of ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/about/about-stack/', + name: 'Redis Stack' + } + }, + { + type: 'span', + value: ', ' + }, + { + type: 'link', + value: { + href: 'https://redis.com/redis-enterprise-cloud/overview/', + name: 'Redis Enterprise Cloud' + } + }, + { + type: 'span', + value: ' and ' + }, + { + type: 'link', + value: { + href: 'https://redis.com/redis-enterprise-software/overview/', + name: 'Redis Enterprise Software' + } + }, + { + type: 'span', + value: '.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'paragraph', + value: 'Try the interactive tutorial to learn more about triggers and functions.' + } + ], + badges: ['code_changes'] + }, + functionsWithKeyspace: { + id: 'functionsWithKeyspace', + title: 'Consider using triggers and functions to react in real-time to database changes', + tutorial: '/quick-guides/triggers-and-functions/introduction.md', + content: [ + { + type: 'paragraph', + value: 'If you need to manipulate your data based on keyspace notifications, consider using keyspace triggers that are a part of triggers and functions. It can help lower latency by moving business logic closer to the data.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'paragraph', + value: "Try triggers and functions to take advantage of Javascript's vast ecosystem of libraries and frameworks and modern, expressive syntax." + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'paragraph', + value: 'These capabilities can execute business logic on changes within a database, and read across all shards in clustered databases.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'span', + value: 'Triggers and functions are part of ' + }, + { + type: 'link', + value: { + href: 'https://redis.io/docs/about/about-stack/', + name: 'Redis Stack' + } + }, + { + type: 'span', + value: ', ' + }, + { + type: 'link', + value: { + href: 'https://redis.com/redis-enterprise-cloud/overview/', + name: 'Redis Enterprise Cloud' + } + }, + { + type: 'span', + value: ' and ' + }, + { + type: 'link', + value: { + href: 'https://redis.com/redis-enterprise-software/overview/', + name: 'Redis Enterprise Software' + } + }, + { + type: 'span', + value: '.' + }, + { + type: 'spacer', + value: 'l' + }, + { + type: 'paragraph', + value: 'Try the interactive tutorial to learn more about triggers and functions.' + } + ], + badges: ['code_changes'] + } +} diff --git a/redisinsight/ui/src/pages/databaseAnalysis/components/data-nav-tabs/DatabaseAnalysisTabs.spec.tsx b/redisinsight/ui/src/pages/databaseAnalysis/components/data-nav-tabs/DatabaseAnalysisTabs.spec.tsx index 1f00ceabe4..f0182abdd6 100644 --- a/redisinsight/ui/src/pages/databaseAnalysis/components/data-nav-tabs/DatabaseAnalysisTabs.spec.tsx +++ b/redisinsight/ui/src/pages/databaseAnalysis/components/data-nav-tabs/DatabaseAnalysisTabs.spec.tsx @@ -8,13 +8,28 @@ import { DatabaseAnalysisViewTab } from 'uiSrc/slices/interfaces/analytics' import { setDatabaseAnalysisViewTab } from 'uiSrc/slices/analytics/dbAnalysis' import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' +import { MOCK_RECOMMENDATIONS } from 'uiSrc/constants/mocks/mock-recommendations' +import { recommendationsSelector } from 'uiSrc/slices/recommendations/recommendations' import DatabaseAnalysisTabs, { Props } from './DatabaseAnalysisTabs' +const mockRecommendationsSelector = jest.requireActual('uiSrc/slices/recommendations/recommendations') + jest.mock('uiSrc/telemetry', () => ({ ...jest.requireActual('uiSrc/telemetry'), sendEventTelemetry: jest.fn(), })) +jest.mock('uiSrc/slices/recommendations/recommendations', () => ({ + ...jest.requireActual('uiSrc/slices/recommendations/recommendations'), + recommendationsSelector: jest.fn().mockReturnValue({ + data: { + recommendations: [], + totalUnread: 0, + }, + isContentVisible: false, + }), +})) + const mockedProps = mock() const mockReports = [ @@ -24,6 +39,8 @@ const mockReports = [ } ] +const recommendationsContent = MOCK_RECOMMENDATIONS + let store: typeof mockedStore jest.mock('uiSrc/slices/instances/instances', () => ({ @@ -37,7 +54,12 @@ jest.mock('uiSrc/slices/instances/instances', () => ({ beforeEach(() => { cleanup() store = cloneDeep(mockedStore) - store.clearActions() + store.clearActions(); + + (recommendationsSelector as jest.Mock).mockImplementation(() => ({ + ...mockRecommendationsSelector, + content: recommendationsContent, + })) }) describe('DatabaseAnalysisTabs', () => { diff --git a/redisinsight/ui/src/pages/databaseAnalysis/components/data-nav-tabs/DatabaseAnalysisTabs.tsx b/redisinsight/ui/src/pages/databaseAnalysis/components/data-nav-tabs/DatabaseAnalysisTabs.tsx index 9f67d08a58..395f073ddc 100644 --- a/redisinsight/ui/src/pages/databaseAnalysis/components/data-nav-tabs/DatabaseAnalysisTabs.tsx +++ b/redisinsight/ui/src/pages/databaseAnalysis/components/data-nav-tabs/DatabaseAnalysisTabs.tsx @@ -7,11 +7,10 @@ import { EmptyAnalysisMessage } from 'uiSrc/pages/databaseAnalysis/components' import { setDatabaseAnalysisViewTab, dbAnalysisViewTabSelector } from 'uiSrc/slices/analytics/dbAnalysis' import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances' import { DatabaseAnalysisViewTab } from 'uiSrc/slices/interfaces/analytics' -import { IRecommendationsStatic } from 'uiSrc/slices/interfaces/recommendations' import { Nullable } from 'uiSrc/utils' import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' import { renderOnboardingTourWithChild } from 'uiSrc/utils/onboarding' -import _content from 'uiSrc/constants/dbAnalysisRecommendations.json' +import { recommendationsSelector } from 'uiSrc/slices/recommendations/recommendations' import { ShortDatabaseAnalysis, DatabaseAnalysis } from 'apiSrc/modules/database-analysis/models' import { databaseAnalysisTabs } from './constants' @@ -23,13 +22,12 @@ export interface Props { data: Nullable } -const recommendationsContent = _content as IRecommendationsStatic - const DatabaseAnalysisTabs = (props: Props) => { const { loading, reports, data } = props const viewTab = useSelector(dbAnalysisViewTabSelector) const { id: instanceId = '', provider } = useSelector(connectedInstanceSelector) + const { content: recommendationsContent } = useSelector(recommendationsSelector) const dispatch = useDispatch() diff --git a/redisinsight/ui/src/pages/databaseAnalysis/components/recommendations-view/Recommendations.spec.tsx b/redisinsight/ui/src/pages/databaseAnalysis/components/recommendations-view/Recommendations.spec.tsx index bdfd5ecc21..330f04a4d5 100644 --- a/redisinsight/ui/src/pages/databaseAnalysis/components/recommendations-view/Recommendations.spec.tsx +++ b/redisinsight/ui/src/pages/databaseAnalysis/components/recommendations-view/Recommendations.spec.tsx @@ -4,10 +4,14 @@ import { fireEvent, render, screen } from 'uiSrc/utils/test-utils' import { dbAnalysisSelector } from 'uiSrc/slices/analytics/dbAnalysis' import { INSTANCE_ID_MOCK } from 'uiSrc/mocks/handlers/analytics/clusterDetailsHandlers' import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' +import { recommendationsSelector } from 'uiSrc/slices/recommendations/recommendations' +import { MOCK_RECOMMENDATIONS } from 'uiSrc/constants/mocks/mock-recommendations' import Recommendations from './Recommendations' +const recommendationsContent = MOCK_RECOMMENDATIONS const mockdbAnalysisSelector = jest.requireActual('uiSrc/slices/analytics/dbAnalysis') +const mockRecommendationsSelector = jest.requireActual('uiSrc/slices/recommendations/recommendations') jest.mock('uiSrc/telemetry', () => ({ ...jest.requireActual('uiSrc/telemetry'), @@ -21,6 +25,17 @@ jest.mock('react-router-dom', () => ({ }), })) +jest.mock('uiSrc/slices/recommendations/recommendations', () => ({ + ...jest.requireActual('uiSrc/slices/recommendations/recommendations'), + recommendationsSelector: jest.fn().mockReturnValue({ + data: { + recommendations: [], + totalUnread: 0, + }, + isContentVisible: false, + }), +})) + jest.mock('uiSrc/slices/workbench/wb-guides', () => ({ ...jest.requireActual('uiSrc/slices/workbench/wb-guides'), workbenchGuidesSelector: jest.fn().mockReturnValue({ @@ -71,6 +86,15 @@ jest.mock('uiSrc/slices/instances/instances', () => ({ }), })) +beforeEach(() => { + (recommendationsSelector as jest.Mock).mockImplementation(() => ({ + ...mockRecommendationsSelector, + data: { recommendations: [{ name: 'RTS' }] }, + isContentVisible: true, + content: recommendationsContent, + })) +}) + describe('Recommendations', () => { it('should render', () => { expect(render()).toBeTruthy() diff --git a/redisinsight/ui/src/pages/databaseAnalysis/components/recommendations-view/Recommendations.tsx b/redisinsight/ui/src/pages/databaseAnalysis/components/recommendations-view/Recommendations.tsx index d70142fcd3..610c8f5863 100644 --- a/redisinsight/ui/src/pages/databaseAnalysis/components/recommendations-view/Recommendations.tsx +++ b/redisinsight/ui/src/pages/databaseAnalysis/components/recommendations-view/Recommendations.tsx @@ -16,7 +16,6 @@ import { import { ThemeContext } from 'uiSrc/contexts/themeContext' import { dbAnalysisSelector } from 'uiSrc/slices/analytics/dbAnalysis' import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances' -import _content from 'uiSrc/constants/dbAnalysisRecommendations.json' import { EAManifestFirstKey, Pages, Theme } from 'uiSrc/constants' import { Vote } from 'uiSrc/constants/recommendations' import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' @@ -27,9 +26,9 @@ import NoRecommendationsLight from 'uiSrc/assets/img/icons/recommendations_light import { workbenchGuidesSelector } from 'uiSrc/slices/workbench/wb-guides' import { workbenchTutorialsSelector } from 'uiSrc/slices/workbench/wb-tutorials' import { resetWorkbenchEASearch, setWorkbenchEAMinimized } from 'uiSrc/slices/app/context' -import { IRecommendationsStatic } from 'uiSrc/slices/interfaces/recommendations' import { EXTERNAL_LINKS } from 'uiSrc/constants/links' import { RecommendationVoting, RecommendationCopyComponent } from 'uiSrc/components' +import { recommendationsSelector } from 'uiSrc/slices/recommendations/recommendations' import { findMarkdownPathByPath, sortRecommendations, @@ -40,13 +39,12 @@ import { import styles from './styles.module.scss' -const recommendationsContent = _content as IRecommendationsStatic - const Recommendations = () => { const { data, loading } = useSelector(dbAnalysisSelector) const { items: guides } = useSelector(workbenchGuidesSelector) const { items: tutorials } = useSelector(workbenchTutorialsSelector) const { provider } = useSelector(connectedInstanceSelector) + const { content: recommendationsContent } = useSelector(recommendationsSelector) const { recommendations = [] } = data ?? {} const { theme } = useContext(ThemeContext) @@ -157,7 +155,7 @@ const Recommendations = () => {
{renderRecommendationBadgesLegend()}
- {sortRecommendations(recommendations).map(({ name, params, vote }) => { + {sortRecommendations(recommendations, recommendationsContent).map(({ name, params, vote }) => { const { id = '', title = '', diff --git a/redisinsight/ui/src/slices/interfaces/recommendations.ts b/redisinsight/ui/src/slices/interfaces/recommendations.ts index cbacfc895a..1d6ae2578c 100644 --- a/redisinsight/ui/src/slices/interfaces/recommendations.ts +++ b/redisinsight/ui/src/slices/interfaces/recommendations.ts @@ -22,6 +22,7 @@ export interface StateRecommendations { error: string, isContentVisible: boolean, isHighlighted: boolean, + content: IRecommendationsStatic } export interface IRecommendationContent { diff --git a/redisinsight/ui/src/slices/recommendations/recommendations.ts b/redisinsight/ui/src/slices/recommendations/recommendations.ts index b5d590a4de..063135b338 100644 --- a/redisinsight/ui/src/slices/recommendations/recommendations.ts +++ b/redisinsight/ui/src/slices/recommendations/recommendations.ts @@ -2,20 +2,21 @@ import { PayloadAction, createSlice } from '@reduxjs/toolkit' import { AxiosError } from 'axios' import { remove } from 'lodash' -import { apiService, localStorageService } from 'uiSrc/services' +import { apiService, localStorageService, resourcesService } from 'uiSrc/services' import { ApiEndpoints, BrowserStorageItem } from 'uiSrc/constants' import { addErrorNotification } from 'uiSrc/slices/app/notifications' import { getApiErrorMessage, getUrl, isStatusSuccessful } from 'uiSrc/utils' import { DeleteDatabaseRecommendationResponse, ModifyDatabaseRecommendationDto } from 'apiSrc/modules/database-recommendation/dto' import { AppDispatch, RootState } from '../store' -import { StateRecommendations, IRecommendations, IRecommendation } from '../interfaces/recommendations' +import { StateRecommendations, IRecommendations, IRecommendation, IRecommendationsStatic } from '../interfaces/recommendations' export const initialState: StateRecommendations = { data: { recommendations: [], totalUnread: 0, }, + content: {}, loading: false, error: '', isContentVisible: false, @@ -78,6 +79,17 @@ const recommendationsSlice = createSlice({ deleteRecommendations: (state, { payload }: PayloadAction) => { remove(state.data.recommendations, (r) => payload.includes(r.id)) }, + + getContentRecommendations: (state) => { + state.loading = true + }, + getContentRecommendationsSuccess: (state, { payload }: PayloadAction) => { + state.loading = false + state.content = payload + }, + getContentRecommendationsFailure: (state) => { + state.loading = false + }, }, }) @@ -95,6 +107,9 @@ export const { updateRecommendationError, setTotalUnread, deleteRecommendations, + getContentRecommendations, + getContentRecommendationsSuccess, + getContentRecommendationsFailure, } = recommendationsSlice.actions // A selector @@ -226,3 +241,20 @@ export function deleteLiveRecommendations( } } } + +// Asynchronous thunk action +export function fetchContentRecommendations() { + return async (dispatch: AppDispatch) => { + dispatch(getContentRecommendations()) + + try { + const { data, status } = await resourcesService + .get(ApiEndpoints.CONTENT_RECOMMENDATIONS) + if (isStatusSuccessful(status)) { + dispatch(getContentRecommendationsSuccess(data)) + } + } catch (_err) { + dispatch(getContentRecommendationsFailure()) + } + } +} diff --git a/redisinsight/ui/src/utils/recommendation/utils.tsx b/redisinsight/ui/src/utils/recommendation/utils.tsx index 75b2998a0e..bd50d3e2a9 100644 --- a/redisinsight/ui/src/utils/recommendation/utils.tsx +++ b/redisinsight/ui/src/utils/recommendation/utils.tsx @@ -10,16 +10,13 @@ import { } from '@elastic/eui' import { SpacerSize } from '@elastic/eui/src/components/spacer/spacer' import cx from 'classnames' -import _content from 'uiSrc/constants/dbAnalysisRecommendations.json' -import { IRecommendationsStatic, IRecommendationContent } from 'uiSrc/slices/interfaces/recommendations' +import { IRecommendationContent, IRecommendationsStatic } from 'uiSrc/slices/interfaces/recommendations' import { ReactComponent as CodeIcon } from 'uiSrc/assets/img/code-changes.svg' import { ReactComponent as ConfigurationIcon } from 'uiSrc/assets/img/configuration-changes.svg' import { ReactComponent as UpgradeIcon } from 'uiSrc/assets/img/upgrade.svg' import styles from './styles.module.scss' -const recommendationsContent = _content as IRecommendationsStatic - const utmSource = 'redisinsight' const utmMedium = 'recommendation' @@ -205,12 +202,13 @@ const renderRecommendationContent = ( ) => ( elements?.map((item, idx) => renderContentElement(item, params, telemetry, insights, idx))) -const sortRecommendations = (recommendations: any[]) => sortBy(recommendations, [ - ({ name }) => name !== 'searchJSON', - ({ name }) => name !== 'searchIndexes', - ({ name }) => recommendationsContent[name]?.redisStack, - ({ name }) => name, -]) +const sortRecommendations = (recommendations: any[], recommendationsContent: IRecommendationsStatic) => + sortBy(recommendations, [ + ({ name }) => name !== 'searchJSON', + ({ name }) => name !== 'searchIndexes', + ({ name }) => recommendationsContent[name]?.redisStack, + ({ name }) => name, + ]) export { addUtmToLink, diff --git a/redisinsight/ui/src/utils/tests/recommendation/utils.spec.tsx b/redisinsight/ui/src/utils/tests/recommendation/utils.spec.tsx index 1b19567c1d..8166f6c8e2 100644 --- a/redisinsight/ui/src/utils/tests/recommendation/utils.spec.tsx +++ b/redisinsight/ui/src/utils/tests/recommendation/utils.spec.tsx @@ -8,6 +8,7 @@ import { renderRecommendationContent, } from 'uiSrc/utils' import { IRecommendationContent } from 'uiSrc/slices/interfaces/recommendations' +import { MOCK_RECOMMENDATIONS } from 'uiSrc/constants/mocks/mock-recommendations' const mockTelemetryName = 'name' @@ -124,7 +125,7 @@ describe('sortRecommendations', () => { test.each(sortRecommendationsTests)( '%j', ({ input, expected }) => { - const result = sortRecommendations(input) + const result = sortRecommendations(input, MOCK_RECOMMENDATIONS) expect(result).toEqual(expected) } ) From baf08042e277c0b0b377295a220155a941eeb987 Mon Sep 17 00:00:00 2001 From: zalenskiSofteq Date: Tue, 25 Jul 2023 19:06:50 +0200 Subject: [PATCH 2/4] #RI-4506 - Update recommendations without the code change --- .../recommendations/recommendations.spec.ts | 114 +++++++++++++++++- 1 file changed, 112 insertions(+), 2 deletions(-) diff --git a/redisinsight/ui/src/slices/tests/recommendations/recommendations.spec.ts b/redisinsight/ui/src/slices/tests/recommendations/recommendations.spec.ts index 341b7ec57e..2ef217b733 100644 --- a/redisinsight/ui/src/slices/tests/recommendations/recommendations.spec.ts +++ b/redisinsight/ui/src/slices/tests/recommendations/recommendations.spec.ts @@ -1,7 +1,8 @@ import { AxiosError } from 'axios' import { cloneDeep, set } from 'lodash' +import { MOCK_RECOMMENDATIONS } from 'uiSrc/constants/mocks/mock-recommendations' import { Vote } from 'uiSrc/constants/recommendations' -import { apiService } from 'uiSrc/services' +import { apiService, resourcesService } from 'uiSrc/services' import { addErrorNotification } from 'uiSrc/slices/app/notifications' import reducer, { initialState, @@ -19,7 +20,12 @@ import reducer, { updateLiveRecommendation, updateRecommendation, setTotalUnread, - deleteLiveRecommendations, deleteRecommendations, + deleteLiveRecommendations, + deleteRecommendations, + getContentRecommendations, + getContentRecommendationsSuccess, + getContentRecommendationsFailure, + fetchContentRecommendations, } from 'uiSrc/slices/recommendations/recommendations' import { cleanup, initialStateDefault, mockStore, mockedStore } from 'uiSrc/utils/test-utils' @@ -261,6 +267,67 @@ describe('recommendations slice', () => { }) expect(recommendationsSelector(rootState)).toEqual(state) }) + + describe('getContentRecommendations', () => { + it('should properly set loading: true', () => { + // Arrange + const state = { + ...initialState, + loading: true, + error: '', + } + + // Act + const nextState = reducer(initialState, getContentRecommendations()) + + // Assert + const rootState = Object.assign(initialStateDefault, { + recommendations: nextState, + }) + expect(recommendationsSelector(rootState)).toEqual(state) + }) + }) + + describe('getContentRecommendationsFailure', () => { + it('should properly set error', () => { + // Arrange + const error = 'Some error' + const state = { + ...initialState, + loading: false, + } + + // Act + const nextState = reducer(initialState, getContentRecommendationsFailure()) + + // Assert + const rootState = Object.assign(initialStateDefault, { + recommendations: nextState, + }) + expect(recommendationsSelector(rootState)).toEqual(state) + }) + }) + + describe('getContentRecommendationsSuccess', () => { + it('should properly set loading: true', () => { + const payload = MOCK_RECOMMENDATIONS + // Arrange + const state = { + ...initialState, + loading: false, + content: MOCK_RECOMMENDATIONS + } + + // Act + const nextState = reducer(initialState, getContentRecommendationsSuccess(payload)) + + // Assert + const rootState = Object.assign(initialStateDefault, { + recommendations: nextState, + }) + expect(recommendationsSelector(rootState)).toEqual(state) + }) + }) }) // thunks @@ -495,5 +562,48 @@ describe('recommendations slice', () => { expect(store.getActions()).toEqual(expectedActions) }) }) + + describe('fetchContentRecommendations', () => { + it('succeed to get content recommendations', async () => { + const data = MOCK_RECOMMENDATIONS + const responsePayload = { status: 200, data } + + resourcesService.get = jest.fn().mockResolvedValue(responsePayload) + + // Act + await store.dispatch(fetchContentRecommendations()) + + // Assert + const expectedActions = [ + getContentRecommendations(), + getContentRecommendationsSuccess(data), + ] + + expect(store.getActions()).toEqual(expectedActions) + }) + + it('failed to get content recommendations', async () => { + const errorMessage = 'Something was wrong!' + const responsePayload = { + response: { + status: 500, + data: { message: errorMessage }, + }, + } + + resourcesService.get = jest.fn().mockRejectedValue(responsePayload) + + // Act + await store.dispatch(fetchContentRecommendations()) + + // Assert + const expectedActions = [ + getContentRecommendations(), + getContentRecommendationsFailure() + ] + + expect(store.getActions()).toEqual(expectedActions) + }) + }) }) }) From 10f17a9ef5a912b953d373b7190c723b741220fe Mon Sep 17 00:00:00 2001 From: zalenskiSofteq Date: Tue, 25 Jul 2023 19:58:26 +0200 Subject: [PATCH 3/4] #RI-4506 - Update recommendations without the code change --- scripts/build-statics.cmd | 4 ++++ scripts/build-statics.sh | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/scripts/build-statics.cmd b/scripts/build-statics.cmd index 813518d7a4..ab9f535127 100644 --- a/scripts/build-statics.cmd +++ b/scripts/build-statics.cmd @@ -3,6 +3,7 @@ :: =============== Plugins =============== set PLUGINS_DIR=".\redisinsight\api\static\plugins" set PLUGINS_VENDOR_DIR=".\redisinsight\api\static\resources\plugins" +set DEFAULTS_CONTENT_DIR=".\redisinsight\api\defaults\content" :: Default plugins assets call node-sass ".\redisinsight\ui\src\styles\main_plugin.scss" ".\vendor\global_styles.css" --output-style compressed @@ -13,6 +14,9 @@ xcopy ".\redisinsight\ui\src\assets\fonts\inconsolata" ".\vendor\fonts\" /s /e / if not exist %PLUGINS_VENDOR_DIR% mkdir %PLUGINS_VENDOR_DIR% xcopy ".\vendor\." "%PLUGINS_VENDOR_DIR%" /s /e /y +if not exist %DEFAULTS_CONTENT_DIR% mkdir %DEFAULTS_CONTENT_DIR% +curl -H "Content-type: application/json" "${RECOMMENDATIONS_CONTENT_RAW_URL}" -o "${DEFAULTS_CONTENT_DIR}/recommendations.json" + :: Build redisearch plugin set REDISEARCH_DIR=".\redisinsight\ui\src\packages\redisearch" call yarn --cwd "%REDISEARCH_DIR%" diff --git a/scripts/build-statics.sh b/scripts/build-statics.sh index 62fb0ba367..14daef83a5 100644 --- a/scripts/build-statics.sh +++ b/scripts/build-statics.sh @@ -5,6 +5,8 @@ set -e PLUGINS_DIR="./redisinsight/api/static/plugins" PLUGINS_VENDOR_DIR="./redisinsight/api/static/resources/plugins" +DEFAULTS_CONTENT_DIR="./redisinsight/api/defaults/content" + # Default plugins assets node-sass "./redisinsight/ui/src/styles/main_plugin.scss" "./vendor/global_styles.css" --output-style compressed; node-sass "./redisinsight/ui/src/styles/themes/dark_theme/_dark_theme.lazy.scss" "./vendor/dark_theme.css" --output-style compressed; @@ -13,6 +15,10 @@ cp -R "./redisinsight/ui/src/assets/fonts/graphik/" "./vendor/fonts" cp -R "./redisinsight/ui/src/assets/fonts/inconsolata/" "./vendor/fonts" mkdir -p "${PLUGINS_VENDOR_DIR}" cp -R "./vendor/." "${PLUGINS_VENDOR_DIR}" +mkdir -p "${DEFAULTS_CONTENT_DIR}" +curl -H "Content-type: application/json" "${RECOMMENDATIONS_CONTENT_RAW_URL}" -o "${DEFAULTS_CONTENT_DIR}/recommendations.json" + +RECOMMENDATIONS_CONTENT_RAW_URL # Build redisearch plugin REDISEARCH_DIR="./redisinsight/ui/src/packages/redisearch" From bfa69e2657a1663237eaefd9c7d23135c56817aa Mon Sep 17 00:00:00 2001 From: zalenskiSofteq Date: Tue, 25 Jul 2023 20:13:36 +0200 Subject: [PATCH 4/4] #RI-4506 - Update recommendations without the code change --- scripts/build-statics.cmd | 2 +- scripts/build-statics.sh | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/build-statics.cmd b/scripts/build-statics.cmd index ab9f535127..87d2d7ed54 100644 --- a/scripts/build-statics.cmd +++ b/scripts/build-statics.cmd @@ -15,7 +15,7 @@ if not exist %PLUGINS_VENDOR_DIR% mkdir %PLUGINS_VENDOR_DIR% xcopy ".\vendor\." "%PLUGINS_VENDOR_DIR%" /s /e /y if not exist %DEFAULTS_CONTENT_DIR% mkdir %DEFAULTS_CONTENT_DIR% -curl -H "Content-type: application/json" "${RECOMMENDATIONS_CONTENT_RAW_URL}" -o "${DEFAULTS_CONTENT_DIR}/recommendations.json" +curl -H "Content-type: application/json" "%RECOMMENDATIONS_CONTENT_RAW_URL%" -o "%DEFAULTS_CONTENT_DIR%/recommendations.json" :: Build redisearch plugin set REDISEARCH_DIR=".\redisinsight\ui\src\packages\redisearch" diff --git a/scripts/build-statics.sh b/scripts/build-statics.sh index 14daef83a5..c9fc8526ec 100644 --- a/scripts/build-statics.sh +++ b/scripts/build-statics.sh @@ -18,8 +18,6 @@ cp -R "./vendor/." "${PLUGINS_VENDOR_DIR}" mkdir -p "${DEFAULTS_CONTENT_DIR}" curl -H "Content-type: application/json" "${RECOMMENDATIONS_CONTENT_RAW_URL}" -o "${DEFAULTS_CONTENT_DIR}/recommendations.json" -RECOMMENDATIONS_CONTENT_RAW_URL - # Build redisearch plugin REDISEARCH_DIR="./redisinsight/ui/src/packages/redisearch" yarn --cwd "${REDISEARCH_DIR}"