Skip to content

Commit b90941f

Browse files
committed
Merge branch 'feature/RI-2932_Consumer_Groups' into feature/RI-2936_modify_last-id
# Conflicts: # redisinsight/ui/src/pages/browser/components/stream-details/groups-view/GroupsViewWrapper.tsx
2 parents f990fac + eb85d65 commit b90941f

File tree

31 files changed

+836
-466
lines changed

31 files changed

+836
-466
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import RangeFilter from './RangeFilter'
2+
3+
export default RangeFilter

redisinsight/ui/src/components/virtual-table/styles.module.scss

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -151,12 +151,6 @@ $footerHeight: 38px;
151151
white-space: pre-wrap;
152152
}
153153

154-
:global(.key-details-table) {
155-
:global(.ReactVirtualized__Table__row) {
156-
font-size: 13px;
157-
}
158-
}
159-
160154
:global(.key-list-table) {
161155
height: calc(100% - 58px);
162156
}
@@ -176,6 +170,9 @@ $footerHeight: 38px;
176170
}
177171
}
178172
}
173+
:global(.ReactVirtualized__Table__row) {
174+
font-size: 13px;
175+
}
179176
:global(.ReactVirtualized__Table__headerRow) {
180177
border: 1px solid var(--tableLightestBorderColor) !important;
181178
}

redisinsight/ui/src/constants/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ enum ApiEndpoints {
3838
STREAMS_CONSUMER_GROUPS = 'streams/consumer-groups',
3939
STREAMS_CONSUMER_GROUPS_GET = 'streams/consumer-groups/get',
4040
STREAMS_CONSUMERS_GET = 'streams/consumer-groups/consumers/get',
41+
STREAMS_CONSUMERS_MESSAGES_GET = 'streams/consumer-groups/consumers/pending-messages/get',
4142
STREAMS = 'streams',
4243
CLI = 'cli',
4344
CLI_BLOCKING_COMMANDS = 'info/cli-blocking-commands',

redisinsight/ui/src/pages/browser/components/stream-details/StreamDetailsWrapper.tsx

Lines changed: 185 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,184 @@
1-
import { EuiProgress, EuiTab, EuiTabs } from '@elastic/eui'
2-
import React from 'react'
1+
import { EuiProgress } from '@elastic/eui'
2+
import React, { useCallback, useEffect, useMemo } from 'react'
33
import { useDispatch, useSelector } from 'react-redux'
4+
import { isNull, last } from 'lodash'
5+
import cx from 'classnames'
46

5-
import { streamSelector, setStreamViewType, streamGroupsSelector } from 'uiSrc/slices/browser/stream'
6-
import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
7+
import {
8+
streamSelector,
9+
streamGroupsSelector,
10+
streamRangeSelector,
11+
streamDataSelector,
12+
fetchMoreStreamEntries,
13+
updateStart,
14+
updateEnd,
15+
fetchStreamEntries
16+
} from 'uiSrc/slices/browser/stream'
717
import { StreamViewType } from 'uiSrc/slices/interfaces/stream'
818

9-
import { streamViewTypeTabs } from './constants'
19+
import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
20+
import { getNextId, getTimestampFromId } from 'uiSrc/utils/streamUtils'
21+
import { SortOrder } from 'uiSrc/constants'
22+
import { SCAN_COUNT_DEFAULT } from 'uiSrc/constants/api'
23+
import { selectedKeyDataSelector } from 'uiSrc/slices/browser/keys'
24+
import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
25+
import { GetStreamEntriesResponse } from 'apiSrc/modules/browser/dto/stream.dto'
26+
import RangeFilter from 'uiSrc/components/range-filter'
1027
import ConsumersViewWrapper from './consumers-view'
1128
import GroupsViewWrapper from './groups-view'
29+
import MessagesViewWrapper from './messages-view'
1230
import StreamDataViewWrapper from './stream-data-view'
1331
import StreamTabs from './stream-tabs'
1432

33+
import styles from './styles.module.scss'
34+
1535
export interface Props {
1636
isFooterOpen: boolean
1737
}
1838

1939
const StreamDetailsWrapper = (props: Props) => {
20-
const { id: instanceId } = useSelector(connectedInstanceSelector)
21-
const { viewType, loading } = useSelector(streamSelector)
40+
const { viewType, loading, sortOrder: entryColumnSortOrder } = useSelector(streamSelector)
2241
const { loading: loadingGroups } = useSelector(streamGroupsSelector)
42+
const { start, end } = useSelector(streamRangeSelector)
43+
const {
44+
firstEntry,
45+
lastEntry,
46+
entries,
47+
} = useSelector(streamDataSelector)
48+
const { name: key } = useSelector(selectedKeyDataSelector) ?? { name: '' }
49+
const { id: instanceId } = useSelector(connectedInstanceSelector)
2350

2451
const dispatch = useDispatch()
2552

53+
const firstEntryTimeStamp = useMemo(() => getTimestampFromId(firstEntry?.id), [firstEntry?.id])
54+
const lastEntryTimeStamp = useMemo(() => getTimestampFromId(lastEntry?.id), [lastEntry?.id])
55+
56+
const startNumber = useMemo(() => (start === '' ? 0 : parseInt(start, 10)), [start])
57+
const endNumber = useMemo(() => (end === '' ? 0 : parseInt(end, 10)), [end])
58+
59+
const shouldFilterRender = !isNull(firstEntry)
60+
&& (firstEntry.id !== '')
61+
&& !isNull(lastEntry)
62+
&& lastEntry.id !== ''
63+
64+
useEffect(() => {
65+
if (isNull(firstEntry)) {
66+
dispatch(updateStart(''))
67+
}
68+
if (start === '' && firstEntry?.id !== '') {
69+
dispatch(updateStart(firstEntryTimeStamp.toString()))
70+
}
71+
}, [firstEntryTimeStamp])
72+
73+
useEffect(() => {
74+
if (isNull(lastEntry)) {
75+
dispatch(updateEnd(''))
76+
}
77+
if (end === '' && lastEntry?.id !== '') {
78+
dispatch(updateEnd(lastEntryTimeStamp.toString()))
79+
}
80+
}, [lastEntryTimeStamp])
81+
82+
const loadMoreItems = () => {
83+
const lastLoadedEntryId = last(entries)?.id
84+
const lastLoadedEntryTimeStamp = getTimestampFromId(lastLoadedEntryId)
85+
86+
const lastRangeEntryTimestamp = end ? parseInt(end, 10) : getTimestampFromId(lastEntry?.id)
87+
const firstRangeEntryTimestamp = start ? parseInt(start, 10) : getTimestampFromId(firstEntry?.id)
88+
const shouldLoadMore = () => {
89+
if (!lastLoadedEntryTimeStamp) {
90+
return false
91+
}
92+
return entryColumnSortOrder === SortOrder.ASC
93+
? lastLoadedEntryTimeStamp <= lastRangeEntryTimestamp
94+
: lastLoadedEntryTimeStamp >= firstRangeEntryTimestamp
95+
}
96+
const nextId = getNextId(lastLoadedEntryId, entryColumnSortOrder)
97+
98+
if (shouldLoadMore()) {
99+
dispatch(
100+
fetchMoreStreamEntries(
101+
key,
102+
entryColumnSortOrder === SortOrder.DESC ? start : nextId,
103+
entryColumnSortOrder === SortOrder.DESC ? nextId : end,
104+
SCAN_COUNT_DEFAULT,
105+
entryColumnSortOrder,
106+
)
107+
)
108+
}
109+
}
110+
111+
const filterTelemetry = (data: GetStreamEntriesResponse) => {
112+
sendEventTelemetry({
113+
event: TelemetryEvent.STREAM_DATA_FILTERED,
114+
eventData: {
115+
databaseId: instanceId,
116+
total: data.total,
117+
}
118+
})
119+
}
120+
121+
const resetFilterTelemetry = (data: GetStreamEntriesResponse) => {
122+
sendEventTelemetry({
123+
event: TelemetryEvent.STREAM_DATA_FILTER_RESET,
124+
eventData: {
125+
databaseId: instanceId,
126+
total: data.total,
127+
}
128+
})
129+
}
130+
131+
const loadEntries = (telemetryAction?: (data: GetStreamEntriesResponse) => void) => {
132+
dispatch(fetchStreamEntries(
133+
key,
134+
SCAN_COUNT_DEFAULT,
135+
entryColumnSortOrder,
136+
false,
137+
telemetryAction
138+
))
139+
}
140+
141+
const handleChangeStartFilter = useCallback(
142+
(value: number, shouldSentEventTelemetry: boolean) => {
143+
dispatch(updateStart(value.toString()))
144+
loadEntries(shouldSentEventTelemetry ? filterTelemetry : undefined)
145+
},
146+
[]
147+
)
148+
149+
const handleChangeEndFilter = useCallback(
150+
(value: number, shouldSentEventTelemetry: boolean) => {
151+
dispatch(updateEnd(value.toString()))
152+
loadEntries(shouldSentEventTelemetry ? filterTelemetry : undefined)
153+
},
154+
[]
155+
)
156+
157+
const handleResetFilter = useCallback(
158+
() => {
159+
dispatch(updateStart(firstEntryTimeStamp.toString()))
160+
dispatch(updateEnd(lastEntryTimeStamp.toString()))
161+
loadEntries(resetFilterTelemetry)
162+
},
163+
[lastEntryTimeStamp, firstEntryTimeStamp]
164+
)
165+
166+
const handleUpdateRangeMin = useCallback(
167+
(min: number) => {
168+
dispatch(updateStart(min.toString()))
169+
},
170+
[]
171+
)
172+
173+
const handleUpdateRangeMax = useCallback(
174+
(max: number) => {
175+
dispatch(updateEnd(max.toString()))
176+
},
177+
[]
178+
)
179+
26180
return (
27-
<>
181+
<div className={styles.container}>
28182
{(loading || loadingGroups) && (
29183
<EuiProgress
30184
color="primary"
@@ -33,17 +187,38 @@ const StreamDetailsWrapper = (props: Props) => {
33187
data-testid="progress-key-stream"
34188
/>
35189
)}
190+
{shouldFilterRender ? (
191+
<RangeFilter
192+
max={lastEntryTimeStamp}
193+
min={firstEntryTimeStamp}
194+
start={startNumber}
195+
end={endNumber}
196+
handleChangeStart={handleChangeStartFilter}
197+
handleChangeEnd={handleChangeEndFilter}
198+
handleResetFilter={handleResetFilter}
199+
handleUpdateRangeMax={handleUpdateRangeMax}
200+
handleUpdateRangeMin={handleUpdateRangeMin}
201+
/>
202+
)
203+
: (
204+
<div className={styles.rangeWrapper}>
205+
<div className={cx(styles.sliderTrack, styles.mockRange)} />
206+
</div>
207+
)}
36208
<StreamTabs />
37209
{viewType === StreamViewType.Data && (
38-
<StreamDataViewWrapper {...props} />
210+
<StreamDataViewWrapper loadMoreItems={loadMoreItems} {...props} />
39211
)}
40212
{viewType === StreamViewType.Groups && (
41213
<GroupsViewWrapper {...props} />
42214
)}
43215
{viewType === StreamViewType.Consumers && (
44216
<ConsumersViewWrapper {...props} />
45217
)}
46-
</>
218+
{viewType === StreamViewType.Messages && (
219+
<MessagesViewWrapper {...props} />
220+
)}
221+
</div>
47222
)
48223
}
49224

redisinsight/ui/src/pages/browser/components/stream-details/consumers-view/ConsumersView/ConsumersView.tsx

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,19 @@ import VirtualTable from 'uiSrc/components/virtual-table/VirtualTable'
1010
import { ITableColumn } from 'uiSrc/components/virtual-table/interfaces'
1111
import { selectedKeyDataSelector } from 'uiSrc/slices/browser/keys'
1212
import { SortOrder } from 'uiSrc/constants'
13-
import { StreamEntryDto } from 'apiSrc/modules/browser/dto/stream.dto'
13+
import { ConsumerDto } from 'apiSrc/modules/browser/dto/stream.dto'
1414

1515
import styles from './styles.module.scss'
1616

1717
const headerHeight = 60
1818
const rowHeight = 54
1919
const actionsWidth = 54
2020
const minColumnWidth = 190
21-
const noItemsMessageString = 'There are no Consumers in the Group.'
22-
23-
interface IStreamEntry extends StreamEntryDto {
24-
editing: boolean
25-
}
21+
const noItemsMessageString = 'Your Consumer Group has no Consumers available.'
2622

2723
export interface Props {
28-
data: IStreamEntry[]
24+
data: ConsumerDto[]
2925
columns: ITableColumn[]
30-
onEditConsumer: (consumerId:string, editing: boolean) => void
3126
onClosePopover: () => void
3227
onSelectConsumer: ({ rowData }: { rowData: any }) => void
3328
isFooterOpen?: boolean
@@ -55,7 +50,7 @@ const ConsumersView = (props: Props) => {
5550
<div
5651
className={cx(
5752
'key-details-table',
58-
'stream-consumers-container',
53+
'stream-details-table',
5954
styles.container,
6055
{ footerOpened: isFooterOpen }
6156
)}

redisinsight/ui/src/pages/browser/components/stream-details/consumers-view/ConsumersView/styles.module.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
display: flex;
33
flex: 1;
44
width: 100%;
5-
padding: 16px 18px;
5+
padding-top: 3px;
66
background-color: var(--euiColorEmptyShade);
77

88
:global {

0 commit comments

Comments
 (0)