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

View File

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

View File

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

View File

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

View File

@ -183,7 +183,7 @@ const useTabStore = defineStore('tab', {
* @param {number} db
* @param {string} key
* @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} [reset]
* @param {boolean} [nocheck] ignore conflict checking for hash/set/zset
@ -225,11 +225,32 @@ const useTabStore = defineStore('tab', {
} else {
tab.value = tab.value || {}
}
for (const k in entries) {
if (nocheck !== true && !tab.value.hasOwnProperty(k)) {
tab.length += 1
if (entries instanceof Array) {
// append new item
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
@ -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
* @param {string} server

View File

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

View File

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