perf: add silent deletion option to batch delete dialog

This commit is contained in:
Lykin 2024-01-23 11:26:15 +08:00
parent ff2043c0e2
commit a679858478
5 changed files with 118 additions and 79 deletions

View File

@ -2073,7 +2073,7 @@ func (b *browserService) DeleteOneKey(server string, db int, k any) (resp types.
}
// DeleteKeys delete keys sync with notification
func (b *browserService) DeleteKeys(server string, db int, ks []any, serialNo string) (resp types.JSResp) {
func (b *browserService) DeleteKeys(server string, db int, ks []any, notice bool, serialNo string) (resp types.JSResp) {
// connect a new connection to export keys
conf := Connection().getConnection(server)
if conf == nil {
@ -2093,7 +2093,7 @@ func (b *browserService) DeleteKeys(server string, db int, ks []any, serialNo st
defer cancelFunc()
cancelEvent := "delete:stop:" + serialNo
runtime.EventsOnce(ctx, cancelEvent, func(data ...any) {
cancelStopEvent := runtime.EventsOnce(ctx, cancelEvent, func(data ...any) {
cancelFunc()
})
processEvent := "deleting:" + serialNo
@ -2104,23 +2104,34 @@ func (b *browserService) DeleteKeys(server string, db int, ks []any, serialNo st
var mutex sync.Mutex
del := func(ctx context.Context, cli redis.UniversalClient) error {
startTime := time.Now().Add(-10 * time.Second)
supportUnlink := true
for i, k := range ks {
// emit progress per second
if i >= total-1 || time.Now().Sub(startTime).Milliseconds() > 100 {
if notice && (i >= total-1 || time.Now().Sub(startTime).Milliseconds() > 100) {
startTime = time.Now()
param := map[string]any{
"total": total,
"progress": i + 1,
"processing": k,
}
runtime.EventsEmit(b.ctx, processEvent, param)
runtime.EventsEmit(ctx, processEvent, param)
// do some sleep to prevent blocking the Redis server
time.Sleep(10 * time.Millisecond)
}
key := strutil.DecodeRedisKey(k)
delErr := cli.Del(ctx, key).Err()
if err != nil {
var delErr error
if supportUnlink {
if delErr = cli.Unlink(ctx, key).Err(); delErr != nil {
supportUnlink = false
delErr = nil
}
}
if !supportUnlink {
delErr = cli.Del(ctx, key).Err()
}
if notice {
if delErr != nil {
failed.Add(1)
} else {
// save deleted key
@ -2128,6 +2139,7 @@ func (b *browserService) DeleteKeys(server string, db int, ks []any, serialNo st
deletedKeys = append(deletedKeys, k)
mutex.Unlock()
}
}
if errors.Is(delErr, context.Canceled) || canceled {
canceled = true
break
@ -2145,7 +2157,7 @@ func (b *browserService) DeleteKeys(server string, db int, ks []any, serialNo st
err = del(ctx, client)
}
runtime.EventsOff(ctx, cancelEvent)
cancelStopEvent()
resp.Success = true
resp.Data = struct {
Canceled bool `json:"canceled"`
@ -2189,8 +2201,7 @@ func (b *browserService) ExportKey(server string, db int, ks []any, path string,
writer := csv.NewWriter(file)
defer writer.Flush()
cancelEvent := "export:stop:" + path
runtime.EventsOnce(ctx, cancelEvent, func(data ...any) {
cancelStopEvent := runtime.EventsOnce(ctx, "export:stop:"+path, func(data ...any) {
cancelFunc()
})
processEvent := "exporting:" + path
@ -2206,7 +2217,7 @@ func (b *browserService) ExportKey(server string, db int, ks []any, path string,
"progress": i + 1,
"processing": k,
}
runtime.EventsEmit(b.ctx, processEvent, param)
runtime.EventsEmit(ctx, processEvent, param)
}
key := strutil.DecodeRedisKey(k)
@ -2230,7 +2241,7 @@ func (b *browserService) ExportKey(server string, db int, ks []any, path string,
}
}
runtime.EventsOff(ctx, cancelEvent)
cancelStopEvent()
resp.Success = true
resp.Data = struct {
Canceled bool `json:"canceled"`
@ -2274,7 +2285,7 @@ func (b *browserService) ImportCSV(server string, db int, path string, conflict
reader := csv.NewReader(file)
cancelEvent := "import:stop:" + path
runtime.EventsOnce(ctx, cancelEvent, func(data ...any) {
cancelStopEvent := runtime.EventsOnce(ctx, cancelEvent, func(data ...any) {
cancelFunc()
})
processEvent := "importing:" + path
@ -2349,7 +2360,7 @@ func (b *browserService) ImportCSV(server string, db int, path string, conflict
}
}
runtime.EventsOff(ctx, cancelEvent)
cancelStopEvent()
resp.Success = true
resp.Data = struct {
Canceled bool `json:"canceled"`

View File

@ -74,7 +74,7 @@ const onConfirmDelete = async () => {
deleting.value = true
const { server, db, key, affectedKeys } = deleteForm
await nextTick()
browserStore.deleteKeys(server, db, affectedKeys).catch((e) => {})
browserStore.deleteKeys(server, db, affectedKeys, !deleteForm.async).catch((e) => {})
} catch (e) {
$message.error(e.message)
return
@ -115,11 +115,9 @@ const onClose = () => {
required>
<n-input v-model:value="deleteForm.key" placeholder="" @input="resetAffected" />
</n-form-item>
<!-- <n-form-item :label="$t('dialogue.key.async_delete')" required>-->
<!-- <n-checkbox v-model:checked="deleteForm.async">-->
<!-- {{ $t('dialogue.key.async_delete_title') }}-->
<!-- </n-checkbox>-->
<!-- </n-form-item>-->
<n-checkbox v-model:checked="deleteForm.async">
{{ $t('dialogue.key.silent') }}
</n-checkbox>
<n-card
v-if="deleteForm.showAffected"
:title="$t('dialogue.key.affected_key') + `(${size(deleteForm.affectedKeys)})`"

View File

@ -263,13 +263,16 @@
"confirm_delete_key": "Confirm Delete {num} Key(s)",
"async_delete": "Asynchronously Execute",
"async_delete_title": "Do not waiting for the operation's result",
"silent": "Do not display deletion status in real time",
"confirm_flush": "I know what I'm doing!",
"confirm_flush_db": "Confirm Flush Database"
},
"delete": {
"success": "\"{key}\" has been deleted",
"deleting": "Deleting",
"doing": "Deleting key({index}/{count})",
"completed": "Deletion process has been completed, {success} successed, {fail} failed"
"completed": "Deletion process has been completed",
"completed_status": "{success} successed, {fail} failed"
},
"field": {
"new": "Add New Field",

View File

@ -263,13 +263,16 @@
"confirm_delete_key": "确认删除{num}个键",
"async_delete": "异步执行",
"async_delete_title": "不等待操作结果",
"silent": "不实时显示删除状态",
"confirm_flush": "我知道我正在执行的操作!",
"confirm_flush_db": "确认清空数据库"
},
"delete": {
"success": "{key} 已被删除",
"deleting": "正在删除",
"doing": "正在删除键({index}/{count})",
"completed": "已完成删除操作,成功{success}个,失败{fail}个"
"completed": "已完成删除操作",
"completed_status": "成功{success}个,失败{fail}个"
},
"field": {
"new": "添加新字段",

View File

@ -44,7 +44,7 @@ import useConnectionStore from 'stores/connections.js'
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
import { isRedisGlob } from '@/utils/glob_pattern.js'
import { i18nGlobal } from '@/utils/i18n.js'
import { EventsEmit, EventsOff, EventsOn } from 'wailsjs/runtime/runtime.js'
import { EventsEmit, EventsOn } from 'wailsjs/runtime/runtime.js'
import { RedisNodeItem } from '@/objects/redisNodeItem.js'
import { RedisServerState } from '@/objects/redisServerState.js'
import { RedisDatabaseItem } from '@/objects/redisDatabaseItem.js'
@ -1614,19 +1614,17 @@ const useBrowserStore = defineStore('browser', {
* @param {string} server
* @param {number} db
* @param {string[]|number[][]} keys
* @param {boolean} notice
* @return {Promise<void>}
*/
async deleteKeys(server, db, keys) {
const msgRef = $message.loading('', { duration: 0, closable: true })
async deleteKeys(server, db, keys, notice) {
const msgRef = $message.loading(i18nGlobal.t('dialogue.delete.deleting'), { duration: 0, closable: true })
let deleted = []
let failCount = 0
let canceled = false
const serialNo = Date.now().valueOf().toString()
const eventName = 'deleting:' + serialNo
const cancelEvent = 'delete:stop:' + serialNo
try {
let maxProgress = 0
EventsOn(eventName, ({ total, progress, processing }) => {
const cancelEventFn = EventsOn('deleting:' + serialNo, ({ total, progress, processing }) => {
// update delete progress
if (progress > maxProgress) {
maxProgress = progress
@ -1639,9 +1637,10 @@ const useBrowserStore = defineStore('browser', {
})
})
msgRef.onClose = () => {
EventsEmit(cancelEvent)
EventsEmit('delete:stop:' + serialNo)
}
const { data, success, msg } = await DeleteKeys(server, db, keys, serialNo)
try {
const { data, success, msg } = await DeleteKeys(server, db, keys, notice, serialNo)
if (success) {
canceled = get(data, 'canceled', false)
deleted = get(data, 'deleted', [])
@ -1650,8 +1649,8 @@ const useBrowserStore = defineStore('browser', {
$message.error(msg)
}
} finally {
cancelEventFn()
msgRef.destroy()
EventsOff(eventName)
// clear checked keys
const tab = useTabStore()
tab.setCheckedKeys(server)
@ -1662,13 +1661,40 @@ const useBrowserStore = defineStore('browser', {
$message.info(i18nGlobal.t('dialogue.handle_cancel'))
} else if (failCount <= 0) {
// no fail
$message.success(i18nGlobal.t('dialogue.delete.completed', { success: deletedCount, fail: failCount }))
let msg = i18nGlobal.t('dialogue.delete.completed')
if (notice) {
msg +=
'\n' +
i18nGlobal.t('dialogue.delete.completed_status', {
success: deletedCount,
fail: failCount,
})
}
$message.success(msg)
} else if (failCount >= deletedCount) {
// all fail
$message.error(i18nGlobal.t('dialogue.delete.completed', { success: deletedCount, fail: failCount }))
let msg = i18nGlobal.t('dialogue.delete.completed')
if (notice) {
msg +=
'\n' +
i18nGlobal.t('dialogue.delete.completed_status', {
success: deletedCount,
fail: failCount,
})
}
$message.error(msg)
} else {
// some fail
$message.warn(i18nGlobal.t('dialogue.delete.completed', { success: deletedCount, fail: failCount }))
let msg = i18nGlobal.t('dialogue.delete.completed')
if (notice) {
msg +=
'\n' +
i18nGlobal.t('dialogue.delete.completed_status', {
success: deletedCount,
fail: failCount,
})
}
$message.warn(msg)
}
// update ui
timeout(100).then(async () => {
@ -1703,9 +1729,7 @@ const useBrowserStore = defineStore('browser', {
let exported = 0
let failCount = 0
let canceled = false
const eventName = 'exporting:' + path
try {
EventsOn(eventName, ({ total, progress, processing }) => {
const cancelEventFn = EventsOn('exporting:' + path, ({ total, progress, processing }) => {
// update export progress
msgRef.content = i18nGlobal.t('dialogue.export.exporting', {
// key: decodeRedisKey(processing),
@ -1716,6 +1740,7 @@ const useBrowserStore = defineStore('browser', {
msgRef.onClose = () => {
EventsEmit('export:stop:' + path)
}
try {
const { data, success, msg } = await ExportKey(server, db, keys, path, expire)
if (success) {
canceled = get(data, 'canceled', false)
@ -1726,7 +1751,7 @@ const useBrowserStore = defineStore('browser', {
}
} finally {
msgRef.destroy()
EventsOff(eventName)
cancelEventFn()
}
if (canceled) {
$message.info(i18nGlobal.t('dialogue.handle_cancel'))
@ -1759,9 +1784,7 @@ const useBrowserStore = defineStore('browser', {
let imported = 0
let ignored = 0
let canceled = false
const eventName = 'importing:' + path
try {
EventsOn(eventName, ({ imported = 0, ignored = 0 }) => {
const cancelEventFn = EventsOn('importing:' + path, ({ imported = 0, ignored = 0 }) => {
// update export progress
msgRef.content = i18nGlobal.t('dialogue.import.importing', {
// key: decodeRedisKey(processing),
@ -1772,6 +1795,7 @@ const useBrowserStore = defineStore('browser', {
msgRef.onClose = () => {
EventsEmit('import:stop:' + path)
}
try {
const { data, success, msg } = await ImportCSV(server, db, path, conflict, ttl)
if (success) {
canceled = get(data, 'canceled', false)
@ -1781,8 +1805,8 @@ const useBrowserStore = defineStore('browser', {
$message.error(msg)
}
} finally {
cancelEventFn()
msgRef.destroy()
EventsOff(eventName)
}
if (canceled) {
$message.info(i18nGlobal.t('dialogue.handle_cancel'))