refactor: refactoring the refresh logic for all complex types after adding new fields

This commit is contained in:
Lykin 2023-11-17 01:24:04 +08:00
parent 45e9c49f26
commit d88fd35e9d
7 changed files with 317 additions and 83 deletions

View File

@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
"net/url" "net/url"
"slices"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
@ -16,7 +17,6 @@ import (
"tinyrdm/backend/consts" "tinyrdm/backend/consts"
"tinyrdm/backend/types" "tinyrdm/backend/types"
"tinyrdm/backend/utils/coll" "tinyrdm/backend/utils/coll"
maputil "tinyrdm/backend/utils/map"
redis2 "tinyrdm/backend/utils/redis" redis2 "tinyrdm/backend/utils/redis"
sliceutil "tinyrdm/backend/utils/slice" sliceutil "tinyrdm/backend/utils/slice"
strutil "tinyrdm/backend/utils/string" strutil "tinyrdm/backend/utils/string"
@ -1147,28 +1147,40 @@ func (b *browserService) AddHashField(connName string, db int, k any, action int
client, ctx := item.client, item.ctx client, ctx := item.client, item.ctx
key := strutil.DecodeRedisKey(k) key := strutil.DecodeRedisKey(k)
updated := map[string]any{} var updated []types.HashEntryItem
var added []types.HashEntryItem
switch action { switch action {
case 1: case 1:
// ignore duplicated fields // ignore duplicated fields
for i := 0; i < len(fieldItems); i += 2 { for i := 0; i < len(fieldItems); i += 2 {
_, err = client.HSetNX(ctx, key, fieldItems[i].(string), fieldItems[i+1]).Result() field, value := strutil.DecodeRedisKey(fieldItems[i]), strutil.DecodeRedisKey(fieldItems[i+1])
if err == nil { if succ, _ := client.HSetNX(ctx, key, field, value).Result(); succ {
updated[fieldItems[i].(string)] = fieldItems[i+1] added = append(added, types.HashEntryItem{
Key: field,
Value: value,
DisplayValue: "", // TODO: convert to display value
})
} }
} }
default: default:
// overwrite duplicated fields // overwrite duplicated fields
total := len(fieldItems) total := len(fieldItems)
if total > 1 { if total > 1 {
_, err = client.Pipelined(ctx, func(pipe redis.Pipeliner) error {
for i := 0; i < total; i += 2 { for i := 0; i < total; i += 2 {
client.HSet(ctx, key, fieldItems[i], fieldItems[i+1]) field, value := strutil.DecodeRedisKey(fieldItems[i]), strutil.DecodeRedisKey(fieldItems[i+1])
} if affect, _ := client.HSet(ctx, key, field, value).Result(); affect > 0 {
return nil added = append(added, types.HashEntryItem{
Key: field,
Value: value,
DisplayValue: "", // TODO: convert to display value
}) })
for i := 0; i < total; i += 2 { } else {
updated[fieldItems[i].(string)] = fieldItems[i+1] updated = append(updated, types.HashEntryItem{
Key: field,
Value: value,
DisplayValue: "", // TODO: convert to display value
})
}
} }
} }
} }
@ -1178,8 +1190,12 @@ func (b *browserService) AddHashField(connName string, db int, k any, action int
} }
resp.Success = true resp.Success = true
resp.Data = map[string]any{ resp.Data = struct {
"updated": updated, Added []types.HashEntryItem `json:"added,omitempty"`
Updated []types.HashEntryItem `json:"updated,omitempty"`
}{
Added: added,
Updated: updated,
} }
return return
} }
@ -1194,16 +1210,27 @@ func (b *browserService) AddListItem(connName string, db int, k any, action int,
client, ctx := item.client, item.ctx client, ctx := item.client, item.ctx
key := strutil.DecodeRedisKey(k) key := strutil.DecodeRedisKey(k)
var leftPush, rightPush []any var leftPush, rightPush []types.ListEntryItem
switch action { switch action {
case 0: case 0:
// push to head // push to head
slices.Reverse(items)
_, err = client.LPush(ctx, key, items...).Result() _, err = client.LPush(ctx, key, items...).Result()
leftPush = append(leftPush, items...) for i := len(items) - 1; i >= 0; i-- {
leftPush = append(leftPush, types.ListEntryItem{
Value: items[i],
DisplayValue: "", // TODO: convert to display value
})
}
default: default:
// append to tail // append to tail
_, err = client.RPush(ctx, key, items...).Result() _, err = client.RPush(ctx, key, items...).Result()
rightPush = append(rightPush, items...) for _, it := range items {
rightPush = append(rightPush, types.ListEntryItem{
Value: it,
DisplayValue: "", // TODO: convert to display value
})
}
} }
if err != nil { if err != nil {
resp.Msg = err.Error() resp.Msg = err.Error()
@ -1211,9 +1238,12 @@ func (b *browserService) AddListItem(connName string, db int, k any, action int,
} }
resp.Success = true resp.Success = true
resp.Data = map[string]any{ resp.Data = struct {
"left": leftPush, Left []types.ListEntryItem `json:"left,omitempty"`
"right": rightPush, Right []types.ListEntryItem `json:"right,omitempty"`
}{
Left: leftPush,
Right: rightPush,
} }
return return
} }
@ -1278,11 +1308,26 @@ func (b *browserService) SetSetItem(server string, db int, k any, remove bool, m
client, ctx := item.client, item.ctx client, ctx := item.client, item.ctx
key := strutil.DecodeRedisKey(k) key := strutil.DecodeRedisKey(k)
var added, removed []types.SetEntryItem
var affected int64 var affected int64
if remove { if remove {
affected, err = client.SRem(ctx, key, members...).Result() for _, member := range members {
if affected, _ = client.SRem(ctx, key, member).Result(); affected > 0 {
removed = append(removed, types.SetEntryItem{
Value: member,
DisplayValue: "", // TODO: convert to display value
})
}
}
} else { } else {
affected, err = client.SAdd(ctx, key, members...).Result() for _, member := range members {
if affected, _ = client.SAdd(ctx, key, member).Result(); affected > 0 {
added = append(added, types.SetEntryItem{
Value: member,
DisplayValue: "", // TODO: convert to display value
})
}
}
} }
if err != nil { if err != nil {
resp.Msg = err.Error() resp.Msg = err.Error()
@ -1290,8 +1335,14 @@ func (b *browserService) SetSetItem(server string, db int, k any, remove bool, m
} }
resp.Success = true resp.Success = true
resp.Data = map[string]any{ resp.Data = struct {
"affected": affected, Added []types.SetEntryItem `json:"added,omitempty"`
Removed []types.SetEntryItem `json:"removed,omitempty"`
Affected int64 `json:"affected"`
}{
Added: added,
Removed: removed,
Affected: affected,
} }
return return
} }
@ -1404,20 +1455,37 @@ func (b *browserService) AddZSetValue(connName string, db int, k any, action int
client, ctx := item.client, item.ctx client, ctx := item.client, item.ctx
key := strutil.DecodeRedisKey(k) key := strutil.DecodeRedisKey(k)
members := maputil.ToSlice(valueScore, func(k string) redis.Z {
return redis.Z{
Score: valueScore[k],
Member: k,
}
})
var added, updated []types.ZSetEntryItem
switch action { switch action {
case 1: case 1:
// ignore duplicated fields // ignore duplicated fields
_, err = client.ZAddNX(ctx, key, members...).Result() for m, s := range valueScore {
if affect, _ := client.ZAddNX(ctx, key, redis.Z{Score: s, Member: m}).Result(); affect > 0 {
added = append(added, types.ZSetEntryItem{
Score: s,
Value: m,
DisplayValue: "", // TODO: convert to display value
})
}
}
default: default:
// overwrite duplicated fields // overwrite duplicated fields
_, err = client.ZAdd(ctx, key, members...).Result() for m, s := range valueScore {
if affect, _ := client.ZAdd(ctx, key, redis.Z{Score: s, Member: m}).Result(); affect > 0 {
added = append(added, types.ZSetEntryItem{
Score: s,
Value: m,
DisplayValue: "", // TODO: convert to display value
})
} else {
updated = append(updated, types.ZSetEntryItem{
Score: s,
Value: m,
DisplayValue: "", // TODO: convert to display value
})
}
}
} }
if err != nil { if err != nil {
resp.Msg = err.Error() resp.Msg = err.Error()
@ -1425,6 +1493,13 @@ func (b *browserService) AddZSetValue(connName string, db int, k any, action int
} }
resp.Success = true resp.Success = true
resp.Data = struct {
Added []types.ZSetEntryItem `json:"added,omitempty"`
Updated []types.ZSetEntryItem `json:"updated,omitempty"`
}{
Added: added,
Updated: updated,
}
return return
} }
@ -1449,9 +1524,24 @@ func (b *browserService) AddStreamValue(connName string, db int, k any, ID strin
return return
} }
updateValues := make(map[string]any, len(fieldItems)/2)
for i := 0; i < len(fieldItems)/2; i += 2 {
updateValues[fieldItems[i].(string)] = fieldItems[i+1]
}
vb, _ := json.Marshal(updateValues)
displayValue, _, _ := strutil.ConvertTo(string(vb), types.DECODE_NONE, types.FORMAT_JSON)
resp.Success = true resp.Success = true
resp.Data = map[string]any{ resp.Data = struct {
"updateID": updateID, Added []types.StreamEntryItem `json:"added,omitempty"`
}{
Added: []types.StreamEntryItem{
{
ID: updateID,
Value: updateValues,
DisplayValue: displayValue, // TODO: convert to display value
},
},
} }
return return
} }

View File

@ -384,7 +384,6 @@ defineExpose({
t-tooltip="interface.load_all_entries" t-tooltip="interface.load_all_entries"
@click="emit('loadall')" /> @click="emit('loadall')" />
</n-button-group> </n-button-group>
{{ valueColumn.align }}
<n-button :focusable="false" plain @click="onAddRow"> <n-button :focusable="false" plain @click="onAddRow">
<template #icon> <template #icon>
<n-icon :component="AddLink" size="18" /> <n-icon :component="AddLink" size="18" />

View File

@ -132,7 +132,7 @@ const exThemeVars = computed(() => {
<n-tooltip :delay="2" :show-arrow="false" placement="right"> <n-tooltip :delay="2" :show-arrow="false" placement="right">
<template #trigger> <template #trigger>
<n-icon :size="iconSize"> <n-icon :size="iconSize">
<component :is="m.icon" :stroke-width="3.5"></component> <component :is="m.icon" :stroke-width="3.5" />
</n-icon> </n-icon>
</template> </template>
{{ m.label }} {{ m.label }}

View File

@ -6,7 +6,6 @@ import {
isEmpty, isEmpty,
join, join,
last, last,
map,
remove, remove,
set, set,
size, size,
@ -1058,21 +1057,26 @@ const useBrowserStore = defineStore('browser', {
/** /**
* insert or update hash field item * insert or update hash field item
* @param {string} connName * @param {string} server
* @param {number} db * @param {number} db
* @param {string|number[]} key * @param {string|number[]} key
* @param {number }action 0:ignore duplicated fields 1:overwrite duplicated fields * @param {number }action 0:ignore duplicated fields 1:overwrite duplicated fields
* @param {string[]} fieldItems field1, value1, filed2, value2... * @param {string[]} fieldItems field1, value1, filed2, value2...
* @returns {Promise<{[msg]: string, success: boolean, [updated]: {}}>} * @returns {Promise<{[msg]: string, success: boolean, [updated]: [], [added]: []}>}
*/ */
async addHashField(connName, db, key, action, fieldItems) { async addHashField(server, db, key, action, fieldItems) {
try { try {
const { data, success, msg } = await AddHashField(connName, db, key, action, fieldItems) const { data, success, msg } = await AddHashField(server, db, key, action, fieldItems)
if (success) { if (success) {
const { updated = {} } = data const { updated = [], added = [] } = data
const tab = useTabStore() const tab = useTabStore()
tab.upsertValueEntries({ server: connName, db, key, type: 'hash', entries: updated }) if (!isEmpty(updated)) {
return { success, updated } tab.replaceValueEntries({ server, db, key, type: 'hash', entries: updated })
}
if (!isEmpty(added)) {
tab.insertValueEntries({ server, db, key, type: 'hash', entries: added })
}
return { success, updated, added }
} else { } else {
return { success: false, msg } return { success: false, msg }
} }
@ -1138,15 +1142,13 @@ const useBrowserStore = defineStore('browser', {
if (success) { if (success) {
const { left = [] } = data const { left = [] } = data
if (!isEmpty(left)) { if (!isEmpty(left)) {
// TODO: convert to display value
const entries = map(left, (v) => ({ v }))
const tab = useTabStore() const tab = useTabStore()
tab.upsertValueEntries({ tab.insertValueEntries({
server: server, server: server,
db, db,
key, key,
type: 'list', type: 'list',
entries, entries: left,
prepend: true, prepend: true,
}) })
} }
@ -1172,15 +1174,15 @@ const useBrowserStore = defineStore('browser', {
const { data, success, msg } = await AddListItem(server, db, key, 1, values) const { data, success, msg } = await AddListItem(server, db, key, 1, values)
if (success) { if (success) {
const { right = [] } = data const { right = [] } = data
// FIXME: do not append items if not all items loaded
if (!isEmpty(right)) { if (!isEmpty(right)) {
const entries = map(right, (v) => ({ v }))
const tab = useTabStore() const tab = useTabStore()
tab.upsertValueEntries({ tab.insertValueEntries({
server: server, server: server,
db, db,
key, key,
type: 'list', type: 'list',
entries, entries: right,
prepend: false, prepend: false,
}) })
} }
@ -1262,21 +1264,24 @@ const useBrowserStore = defineStore('browser', {
/** /**
* add item to set * add item to set
* @param {string} connName * @param {string} server
* @param {number} db * @param {number} db
* @param {string|number} key * @param {string|number} key
* @param {string|string[]} value * @param {string|string[]} value
* @returns {Promise<{[msg]: string, success: boolean}>} * @returns {Promise<{[msg]: string, success: boolean}>}
*/ */
async addSetItem(connName, db, key, value) { async addSetItem(server, db, key, value) {
try { try {
if ((!value) instanceof Array) { if ((!value) instanceof Array) {
value = [value] value = [value]
} }
const { data, success, msg } = await SetSetItem(connName, db, key, false, value) const { data, success, msg } = await SetSetItem(server, db, key, false, value)
if (success) { if (success) {
const { added } = data
if (!isEmpty(added)) {
const tab = useTabStore() const tab = useTabStore()
tab.upsertValueEntries({ server: connName, db, key, type: 'set', entries: value }) tab.insertValueEntries({ server, db, key, type: 'set', entries: added })
}
return { success } return { success }
} else { } else {
return { success, msg } return { success, msg }
@ -1338,19 +1343,25 @@ const useBrowserStore = defineStore('browser', {
/** /**
* add item to sorted set * add item to sorted set
* @param {string} connName * @param {string} server
* @param {number} db * @param {number} db
* @param {string|number[]} key * @param {string|number[]} key
* @param {number} action * @param {number} action
* @param {Object.<string, number>} vs value: score * @param {Object.<string, number>} vs value: score
* @returns {Promise<{[msg]: string, success: boolean}>} * @returns {Promise<{[msg]: string, success: boolean}>}
*/ */
async addZSetItem(connName, db, key, action, vs) { async addZSetItem(server, db, key, action, vs) {
try { try {
const { success, msg } = await AddZSetValue(connName, db, key, action, vs) const { data, success, msg } = await AddZSetValue(server, db, key, action, vs)
const tab = useTabStore()
tab.upsertValueEntries({ server: connName, db, key, type: 'zset', entries: vs })
if (success) { if (success) {
const { added, updated } = data
const tab = useTabStore()
if (!isEmpty(added)) {
tab.insertValueEntries({ server, db, key, type: 'zset', entries: added })
}
if (!isEmpty(updated)) {
tab.replaceValueEntries({ server, db, key, type: 'zset', entries: updated })
}
return { success } return { success }
} else { } else {
return { success, msg } return { success, msg }
@ -1439,26 +1450,28 @@ const useBrowserStore = defineStore('browser', {
/** /**
* insert new stream field item * insert new stream field item
* @param {string} connName * @param {string} server
* @param {number} db * @param {number} db
* @param {string|number[]} key * @param {string|number[]} key
* @param {string} id * @param {string} id
* @param {string[]} values field1, value1, filed2, value2... * @param {string[]} values field1, value1, filed2, value2...
* @returns {Promise<{[msg]: string, success: boolean}>} * @returns {Promise<{[msg]: string, success: boolean}>}
*/ */
async addStreamValue(connName, db, key, id, values) { async addStreamValue(server, db, key, id, values) {
try { try {
const { data = {}, success, msg } = await AddStreamValue(connName, db, key, id, values) const { data = {}, success, msg } = await AddStreamValue(server, db, key, id, values)
if (success) { if (success) {
const { updateID } = data const { added = [] } = data
if (!isEmpty(added)) {
const tab = useTabStore() const tab = useTabStore()
tab.upsertValueEntries({ tab.insertValueEntries({
server: connName, server,
db, db,
key, key,
type: 'stream', type: 'stream',
entries: [{ id: updateID, value: values }], entries: added,
}) })
}
return { success } return { success }
} else { } else {
return { success: false, msg } return { success: false, msg }
@ -1483,8 +1496,8 @@ const useBrowserStore = defineStore('browser', {
try { try {
const { data = {}, success, msg } = await RemoveStreamValues(connName, db, key, ids) const { data = {}, success, msg } = await RemoveStreamValues(connName, db, key, ids)
if (success) { if (success) {
const tab = useTabStore() // const tab = useTabStore()
tab.removeValueEntries({ server: connName, db, key, type: 'stream', entries: ids }) // tab.removeValueEntries({ server: connName, db, key, type: 'stream', entries: ids })
return { success } return { success }
} else { } else {
return { success, msg } return { success, msg }

View File

@ -183,7 +183,7 @@ const useTabStore = defineStore('tab', {
* @param {number} db * @param {number} db
* @param {string} key * @param {string} key
* @param {string} type * @param {string} type
* @param {string[]|Object.<string, number>|Object.<number, string>} entries * @param {string[]|Object.<string, number>|Object.<number, string>|{k:string, v:string}[]} entries
* @param {boolean} [prepend] for list only * @param {boolean} [prepend] for list only
* @param {boolean} [reset] * @param {boolean} [reset]
* @param {boolean} [nocheck] ignore conflict checking for hash/set/zset * @param {boolean} [nocheck] ignore conflict checking for hash/set/zset
@ -225,11 +225,32 @@ const useTabStore = defineStore('tab', {
} else { } else {
tab.value = tab.value || {} tab.value = tab.value || {}
} }
for (const k in entries) { if (entries instanceof Array) {
if (nocheck !== true && !tab.value.hasOwnProperty(k)) { // append new item
tab.length += 1 if (reset === true) {
tab.value = entries
} else {
tab.value = tab.value || []
tab.value.push(...entries)
}
tab.length += size(entries)
} else {
// replace item {key: value}
for (const ent in entries) {
let found = false
for (const elem of tab.value) {
if (elem.k === ent) {
elem.v = entries[elem.k]
elem.dv = ent.dv
found = true
break
}
}
if (!found && nocheck !== true) {
tab.length += 1
tab.value.push(ent)
}
} }
tab.value[k] = entries[k]
} }
break break
@ -294,6 +315,117 @@ const useTabStore = defineStore('tab', {
} }
}, },
/**
* insert entries
* @param {string} server
* @param {number} db
* @param {string|number[]} key
* @param {string} type
* @param {any[]} entries
* @param {boolean} [prepend] for list only
*/
insertValueEntries({ server, db, key, type, entries, prepend }) {
const tab = find(this.tabList, { name: server, db, key })
if (tab == null) {
return
}
switch (type.toLowerCase()) {
case 'list': // {v:string, dv:[string]}[]
tab.value = tab.value || []
if (prepend === true) {
tab.value = [...entries, ...tab.value]
} else {
tab.value.push(...entries)
}
tab.length += size(entries)
break
case 'hash': // {k:string, v:string, dv:[string]}[]
case 'set': // {v: string, s: number}[]
case 'zset': // {v: string, s: number}[]
tab.value = tab.value || []
tab.value.push(...entries)
tab.length += size(entries)
break
case 'stream': // {id: string, v: {}}[]
tab.value = tab.value || []
tab.value = [...entries, ...tab.value]
tab.length += size(entries)
break
}
},
/**
* replace entries
* @param {string} server
* @param {number} db
* @param {string|number[]} key
* @param {string} type
* @param {any[]} entries
*/
replaceValueEntries({ server, db, key, type, entries }) {
const tab = find(this.tabList, { name: server, db, key })
if (tab == null) {
return
}
switch (type.toLowerCase()) {
case 'list': // {index:number, v:string, dv:[string]}[]
tab.value = tab.value || []
for (const entry of entries) {
if (size(tab.value) < entry.index) {
tab.value[entry.index] = entry
} else {
// out of range, append
tab.value.push(entry)
tab.length += 1
}
}
break
case 'hash': // {k:string, v:string, dv:string}[]
tab.value = tab.value || []
for (const entry of entries) {
let updated = false
for (const val of tab.value) {
if (val.k === entry.k) {
val.v = entry.v
val.dv = entry.dv
updated = true
break
}
}
if (!updated) {
// no match element, append
tab.value.push(entry)
tab.length += 1
}
}
break
case 'zset': // {s:number, v:string, dv:string}[]
tab.value = tab.value || []
for (const entry of entries) {
let updated = false
for (const val of tab.value) {
if (val.v === entry.v) {
val.s = entry.s
val.dv = entry.dv
updated = true
break
}
}
if (!updated) {
// no match element, append
tab.value.push(entry)
tab.length += 1
}
}
}
},
/** /**
* remove value entries * remove value entries
* @param {string} server * @param {string} server

View File

@ -106,6 +106,9 @@ export async function setupDiscreteApi() {
messageProviderProps: { messageProviderProps: {
placement: 'bottom', placement: 'bottom',
keepAliveOnHover: true, keepAliveOnHover: true,
containerStyle: {
marginBottom: '38px',
},
}, },
notificationProviderProps: { notificationProviderProps: {
max: 5, max: 5,

View File

@ -57,9 +57,6 @@ export const themeOverrides = {
buttonColorActive: '#D13B37', buttonColorActive: '#D13B37',
buttonTextColorActive: '#FFF', buttonTextColorActive: '#FFF',
}, },
Message: {
margin: '0 0 38px 0',
},
DataTable: { DataTable: {
thPaddingSmall: '6px 8px', thPaddingSmall: '6px 8px',
tdPaddingSmall: '6px 8px', tdPaddingSmall: '6px 8px',