Skip to content

Commit 6355f4c

Browse files
authored
Merge pull request #1537 from RedisInsight/ri-explain-plugin
[Plugin] EXPLAIN/PROFILE Visualization
2 parents 9936d2c + eb75d62 commit 6355f4c

File tree

27 files changed

+5243
-2
lines changed

27 files changed

+5243
-2
lines changed
Lines changed: 3 additions & 0 deletions
Loading

redisinsight/ui/src/components/query-card/QueryCard.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import cx from 'classnames'
44
import { EuiLoadingContent, keys } from '@elastic/eui'
55
import { useParams } from 'react-router-dom'
66

7-
import { WBQueryType } from 'uiSrc/pages/workbench/constants'
7+
import { WBQueryType, ProfileQueryType } from 'uiSrc/pages/workbench/constants'
88
import { RunQueryMode, ResultsMode, ResultsSummary } from 'uiSrc/slices/interfaces/workbench'
99
import {
1010
getWBQueryType,
@@ -44,6 +44,7 @@ export interface Props {
4444
onQueryDelete: () => void
4545
onQueryReRun: () => void
4646
onQueryOpen: () => void
47+
onQueryProfile: (type: ProfileQueryType) => void
4748
}
4849

4950
const getDefaultPlugin = (views: IPluginVisualization[], query: string) =>
@@ -75,6 +76,7 @@ const QueryCard = (props: Props) => {
7576
createdAt,
7677
onQueryOpen,
7778
onQueryDelete,
79+
onQueryProfile,
7880
onQueryReRun,
7981
loading,
8082
emptyCommand,
@@ -186,6 +188,7 @@ const QueryCard = (props: Props) => {
186188
setSelectedValue={changeViewTypeSelected}
187189
onQueryDelete={onQueryDelete}
188190
onQueryReRun={onQueryReRun}
191+
onQueryProfile={onQueryProfile}
189192
/>
190193
{isOpen && (
191194
<>

redisinsight/ui/src/components/query-card/QueryCardHeader/QueryCardHeader.tsx

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import { numberWithSpaces } from 'uiSrc/utils/numbers'
3232
import { ThemeContext } from 'uiSrc/contexts/themeContext'
3333
import { appPluginsSelector } from 'uiSrc/slices/app/plugins'
3434
import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
35-
import { getViewTypeOptions, WBQueryType } from 'uiSrc/pages/workbench/constants'
35+
import { getViewTypeOptions, WBQueryType, getProfileViewTypeOptions, ProfileQueryType, isCommandAllowedForProfile } from 'uiSrc/pages/workbench/constants'
3636
import { IPluginVisualization } from 'uiSrc/slices/interfaces'
3737
import { RunQueryMode, ResultsMode, ResultsSummary } from 'uiSrc/slices/interfaces/workbench'
3838
import { appRedisCommandsSelector } from 'uiSrc/slices/app/redis-commands'
@@ -70,6 +70,7 @@ export interface Props {
7070
setSelectedValue: (type: WBQueryType, value: string) => void
7171
onQueryDelete: () => void
7272
onQueryReRun: () => void
73+
onQueryProfile: (type: ProfileQueryType) => void
7374
}
7475

7576
const getExecutionTimeString = (value: number): string => {
@@ -109,6 +110,7 @@ const QueryCardHeader = (props: Props) => {
109110
setSelectedValue,
110111
onQueryDelete,
111112
onQueryReRun,
113+
onQueryProfile,
112114
db,
113115
} = props
114116

@@ -237,6 +239,30 @@ const QueryCardHeader = (props: Props) => {
237239
}
238240
})
239241

242+
const profileOptions: EuiSuperSelectOption<any>[] = (getProfileViewTypeOptions() as any[]).map((item) => {
243+
const { value, id, text } = item
244+
return {
245+
value: id ?? value,
246+
inputDisplay: (
247+
<div className={cx(styles.dropdownOption, styles.dropdownProfileOption)}>
248+
<EuiIcon
249+
className={styles.iconDropdownOption}
250+
type="visTagCloud"
251+
data-testid={`view-type-selected-${value}-${id}`}
252+
/>
253+
</div>
254+
),
255+
dropdownDisplay: (
256+
<div className={cx(styles.dropdownOption, styles.dropdownProfileOption)}>
257+
<span>{truncateText(text, 20)}</span>
258+
</div>
259+
),
260+
'data-test-subj': `profile-type-option-${value}-${id}`,
261+
}
262+
})
263+
264+
const canCommandProfile = isCommandAllowedForProfile(query)
265+
240266
const indexForSeparator = findIndex(pluginsOptions, (option) => !option.internal)
241267
if (indexForSeparator > -1) {
242268
modifiedOptions.splice(indexForSeparator + 1, 0, {
@@ -320,6 +346,26 @@ const QueryCardHeader = (props: Props) => {
320346
</EuiToolTip>
321347
)}
322348
</EuiFlexItem>
349+
<EuiFlexItem
350+
grow={false}
351+
className={cx(styles.buttonIcon, styles.viewTypeIcon)}
352+
onClick={onDropDownViewClick}
353+
>
354+
{isOpen && canCommandProfile && !summaryText && (
355+
<div className={styles.dropdownWrapper}>
356+
<div className={styles.dropdown}>
357+
<EuiSuperSelect
358+
options={profileOptions}
359+
itemClassName={cx(styles.changeViewItem, styles.dropdownProfileItem)}
360+
className={cx(styles.changeView, styles.dropdownProfileIcon)}
361+
valueOfSelected={ProfileQueryType.Profile}
362+
onChange={(value: ProfileQueryType) => onQueryProfile(value)}
363+
data-testid="run-profile-type"
364+
/>
365+
</div>
366+
</div>
367+
)}
368+
</EuiFlexItem>
323369
<EuiFlexItem
324370
grow={false}
325371
className={cx(styles.buttonIcon, styles.viewTypeIcon)}

redisinsight/ui/src/components/query-card/QueryCardHeader/styles.module.scss

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,27 @@ $marginIcon: 12px;
127127
height: 40px;
128128
}
129129

130+
.dropdownProfileIcon {
131+
padding: inherit !important;
132+
:global {
133+
.euiSuperSelectControl.euiFormControlLayoutIcons {
134+
display: none !important;
135+
}
136+
}
137+
}
138+
139+
.dropdownProfileOption {
140+
display: inherit !important;
141+
}
142+
143+
.dropdownProfileItem {
144+
:global {
145+
.euiContextMenu__icon {
146+
display: none !important;
147+
}
148+
}
149+
}
150+
130151
.dropdown {
131152
width: 168px;
132153
position: absolute;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# RI-Explain plugin
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
{
2+
"author": {
3+
"name": "Redis Ltd.",
4+
"email": "[email protected]",
5+
"url": "https://redis.com/redis-enterprise/redis-insight"
6+
},
7+
"bugs": {
8+
"url": "https://github.com/"
9+
},
10+
"description": "Show Profile/Explain Visualization",
11+
"source": "./src/main.tsx",
12+
"styles": "./dist/styles.css",
13+
"main": "./dist/index.js",
14+
"name": "explain-plugin",
15+
"version": "0.0.1",
16+
"scripts": {
17+
"start": "cross-env NODE_ENV=development parcel serve src/index.html",
18+
"build": "rimraf dist && cross-env NODE_ENV=production concurrently \"yarn build:js && yarn minify:js\" \"yarn build:css\" \"yarn build:assets\"",
19+
"build-lite": "rm dist/*.js && cross-env NODE_ENV=production concurrently \"yarn build:js && yarn minify:js\"",
20+
"build:js": "parcel build src/main.tsx --dist-dir dist",
21+
"build:css": "parcel build src/styles/styles.less --dist-dir dist",
22+
"build:assets": "parcel build src/assets/**/* --dist-dir dist",
23+
"minify:js": "terser -- dist/main.js > dist/index.js && rimraf dist/main.js"
24+
},
25+
"targets": {
26+
"main": false,
27+
"module": {
28+
"includeNodeModules": true
29+
}
30+
},
31+
"visualizations": [
32+
{
33+
"id": "profile-explain-viz",
34+
"name": "Visualization",
35+
"activationMethod": "renderCore",
36+
"matchCommands": [
37+
"FT.EXPLAIN",
38+
"FT.EXPLAINCLI",
39+
"FT.PROFILE",
40+
"GRAPH.EXPLAIN",
41+
"GRAPH.PROFILE"
42+
],
43+
"iconDark": "./dist/profile_icon_dark.svg",
44+
"iconLight": "./dist/profile_icon_light.svg",
45+
"description": "Profile/Explain plugin Visualization",
46+
"default": true
47+
}
48+
],
49+
"devDependencies": {
50+
"@parcel/compressor-brotli": "^2.0.0",
51+
"@parcel/compressor-gzip": "^2.0.0",
52+
"@parcel/transformer-less": "^2.3.2",
53+
"concurrently": "^6.3.0",
54+
"cross-env": "^7.0.3",
55+
"parcel": "^2.0.0",
56+
"rimraf": "^3.0.2",
57+
"terser": "^5.9.0"
58+
},
59+
"dependencies": {
60+
"@antv/hierarchy": "^0.6.8",
61+
"@antv/x6": "^2.1.3",
62+
"@antv/x6-react-shape": "^2.1.0",
63+
"@elastic/eui": "34.6.0",
64+
"@emotion/react": "^11.7.1",
65+
"classnames": "^2.3.1",
66+
"prop-types": "^15.8.1",
67+
"react": "^18.2.0",
68+
"react-dom": "^18.2.0",
69+
"uuid": "^9.0.0"
70+
}
71+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import React from 'react'
2+
import Explain from './Explain'
3+
4+
const isDarkTheme = document.body.classList.contains('theme_DARK')
5+
6+
export function App(props: { command?: string, data: any }) {
7+
8+
const ErrorResponse = HandleError(props)
9+
10+
if (ErrorResponse !== null) return ErrorResponse
11+
12+
return (
13+
<div id="mainApp" style={{ height: "100%", width: '100%', overflowX: 'auto' }}>
14+
<Explain command={props.command || ''} data={props.data}/>
15+
</div>
16+
)
17+
}
18+
19+
function HandleError(props: { command?: string, data: any }): JSX.Element | null {
20+
const { data: [{ response = '', status = '' } = {}] = [] } = props
21+
22+
if (status === 'fail') {
23+
return <div className="responseFail">{JSON.stringify(response)}</div>
24+
}
25+
26+
return null
27+
}

0 commit comments

Comments
 (0)