- {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/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)
+ })
+ })
})
})
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)
}
)
diff --git a/scripts/build-statics.cmd b/scripts/build-statics.cmd
index 813518d7a4..87d2d7ed54 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..c9fc8526ec 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,8 @@ 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"
# Build redisearch plugin
REDISEARCH_DIR="./redisinsight/ui/src/packages/redisearch"