Browse Source

Merge pull request #9959 from nocodb/nc-select-all

fix: delete all records
pull/9988/head
Anbarasu 7 days ago committed by GitHub
parent
commit
69eb9b6376
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 59
      packages/nc-gui/components/dlg/Record/DeleteAll.vue
  2. 0
      packages/nc-gui/components/dlg/Record/Upsert.vue
  3. 145
      packages/nc-gui/components/smartsheet/grid/InfiniteTable.vue
  4. 4
      packages/nc-gui/components/smartsheet/grid/index.vue
  5. 21
      packages/nc-gui/composables/useGridViewData.ts
  6. 3
      packages/nc-gui/composables/useInfiniteData.ts
  7. 1
      packages/nc-gui/lang/en.json
  8. 1
      packages/nocodb/src/controllers/bulk-data-alias.controller.ts
  9. 16
      packages/nocodb/src/schema/swagger.json

59
packages/nc-gui/components/dlg/Record/DeleteAll.vue

@ -0,0 +1,59 @@
<script setup lang="ts">
import { onKeyDown } from '@vueuse/core'
const props = defineProps<{
modelValue: boolean
rows: number
}>()
const emit = defineEmits(['cancel', 'update:modelValue', 'deleteAll'])
const dialogShow = useVModel(props, 'modelValue', emit)
onKeyDown('esc', () => {
dialogShow.value = false
emit('update:modelValue', false)
})
const close = () => {
dialogShow.value = false
emit('cancel')
}
</script>
<template>
<NcModal
v-if="dialogShow"
v-model:visible="dialogShow"
:show-separator="false"
:header="$t('activity.deleteAllRecords')"
size="small"
@keydown.esc="dialogShow = false"
>
<div class="flex justify-between w-full text-base font-semibold mb-2 text-nc-content-gray-emphasis items-center">
{{ 'Delete all records' }}
</div>
<div data-testid="nc-expand-table-modal" class="flex flex-col">
<div class="mb-2 nc-content-gray">Are you sure you want to delete all {{ rows }} records present in this view?</div>
</div>
<div class="bg-nc-bg-gray-light py-2 px-4 flex items-center gap-4 w-full rounded-lg">
<div class="leading-5 text-nc-content-gray">This action cannot be undone.</div>
</div>
<div class="flex flex-row mt-5 justify-end gap-x-2">
<div class="flex gap-2 items-center">
<NcButton data-testid="nn-record-delete-cancel" type="secondary" size="small" @click="close">
{{ $t('labels.cancel') }}
</NcButton>
</div>
<div class="flex gap-2 items-center">
<NcButton data-testid="nc-record-delete-all" type="danger" size="small" @click="emit('deleteAll')">
{{ $t('general.delete') }}
</NcButton>
</div>
</div>
</NcModal>
</template>
<style scoped lang="scss"></style>

0
packages/nc-gui/components/dlg/ExpandTable.vue → packages/nc-gui/components/dlg/Record/Upsert.vue

145
packages/nc-gui/components/smartsheet/grid/InfiniteTable.vue

@ -41,6 +41,7 @@ const props = defineProps<{
metas?: { metaValue?: TableType; viewMetaValue?: ViewType }, metas?: { metaValue?: TableType; viewMetaValue?: ViewType },
undo?: boolean, undo?: boolean,
) => Promise<void> ) => Promise<void>
bulkDeleteAll?: () => Promise<void>
bulkUpsertRows?: ( bulkUpsertRows?: (
insertRows: Row[], insertRows: Row[],
updateRows: [], updateRows: [],
@ -57,9 +58,12 @@ const props = defineProps<{
selectedRows: Array<Row> selectedRows: Array<Row>
chunkStates: Array<'loading' | 'loaded' | undefined> chunkStates: Array<'loading' | 'loaded' | undefined>
isBulkOperationInProgress: boolean isBulkOperationInProgress: boolean
selectedAllRecords?: boolean
}>() }>()
const emits = defineEmits(['bulkUpdateDlg']) const emits = defineEmits(['bulkUpdateDlg', 'update:selectedAllRecords'])
const vSelectedAllRecords = useVModel(props, 'selectedAllRecords', emits)
const { const {
loadData, loadData,
@ -75,6 +79,7 @@ const {
removeRowIfNew, removeRowIfNew,
clearInvalidRows, clearInvalidRows,
applySorting, applySorting,
bulkDeleteAll,
} = props } = props
// Injections // Injections
@ -660,6 +665,29 @@ const onActiveCellChanged = () => {
} }
} }
const isDeleteAllModalIsOpen = ref(false)
async function deleteAllRecords() {
isDeleteAllModalIsOpen.value = true
function closeDlg() {
isOpen.value = false
close(200)
}
const { close } = useDialog(resolveComponent('DlgRecordDeleteAll'), {
'modelValue': isDeleteAllModalIsOpen,
'rows': totalRows.value,
'onUpdate:modelValue': closeDlg,
'onDeleteAll': async () => {
await bulkDeleteAll?.()
closeDlg()
vSelectedAllRecords.value = false
},
})
await until(isDeleteAllModalIsOpen).toBe(false)
}
const isOpen = ref(false) const isOpen = ref(false)
async function expandRows({ async function expandRows({
newRows, newRows,
@ -677,7 +705,7 @@ async function expandRows({
continue: false, continue: false,
expand: true, expand: true,
} }
const { close } = useDialog(resolveComponent('DlgExpandTable'), { const { close } = useDialog(resolveComponent('DlgRecordUpsert'), {
'modelValue': isOpen, 'modelValue': isOpen,
'newRows': newRows, 'newRows': newRows,
'newColumns': newColumns, 'newColumns': newColumns,
@ -1746,6 +1774,26 @@ watch(
immediate: true, immediate: true,
}, },
) )
const toggleRowSelection = (row: number) => {
if (vSelectedAllRecords.value) return
const data = cachedRows.value.get(row)
if (!data) return
data.rowMeta.selected = !data.rowMeta?.selected
cachedRows.value.set(row, data)
}
watch(vSelectedAllRecords, (selectedAll) => {
if (!selectedAll) {
for (const [row, data] of cachedRows.value.entries()) {
if (data.rowMeta?.selected) {
data.rowMeta.selected = false
cachedRows.value.set(row, data)
}
}
}
})
</script> </script>
<template> <template>
@ -1778,7 +1826,7 @@ watch(
<div ref="gridWrapper" class="nc-grid-wrapper min-h-0 flex-1 relative !overflow-auto"> <div ref="gridWrapper" class="nc-grid-wrapper min-h-0 flex-1 relative !overflow-auto">
<NcDropdown <NcDropdown
v-model:visible="contextMenu" v-model:visible="contextMenu"
:disabled="contextMenuTarget === null && !selectedRows.length" :disabled="contextMenuTarget === null && !selectedRows.length && !vSelectedAllRecords"
:trigger="isSqlView ? [] : ['contextmenu']" :trigger="isSqlView ? [] : ['contextmenu']"
overlay-class-name="nc-dropdown-grid-context-menu" overlay-class-name="nc-dropdown-grid-context-menu"
> >
@ -1825,7 +1873,29 @@ watch(
}" }"
data-testid="grid-id-column" data-testid="grid-id-column"
> >
<div class="w-full h-full text-gray-500 flex pl-2 pr-1 items-center" data-testid="nc-check-all">#</div> <div
v-if="!readOnly"
data-testid="nc-check-all"
class="flex items-center pl-2 pr-1 w-full h-full justify-center"
>
<div class="nc-no-label text-gray-500" :class="{ hidden: vSelectedAllRecords }">
#
</div>
<div
:class="{
hidden: !vSelectedAllRecords,
flex: vSelectedAllRecords,
}"
class="nc-check-all w-full items-center"
>
<NcCheckbox v-model:checked="vSelectedAllRecords" />
<span class="flex-1" />
</div>
</div>
<template v-else>
<div class="w-full h-full text-gray-500 flex pl-2 pr-1 items-center" data-testid="nc-check-all">#</div>
</template>
</th> </th>
<th <th
v-if="fields[0] && fields[0].id" v-if="fields[0] && fields[0].id"
@ -2127,7 +2197,7 @@ watch(
'active-row': 'active-row':
activeCell.row === row.rowMeta.rowIndex || selectedRange._start?.row === row.rowMeta.rowIndex, activeCell.row === row.rowMeta.rowIndex || selectedRange._start?.row === row.rowMeta.rowIndex,
'mouse-down': isGridCellMouseDown || isFillMode, 'mouse-down': isGridCellMouseDown || isFillMode,
'selected-row': row.rowMeta.selected, 'selected-row': row.rowMeta.selected || vSelectedAllRecords,
'invalid-row': row.rowMeta?.isValidationFailed || row.rowMeta?.isRowOrderUpdated, 'invalid-row': row.rowMeta?.isValidationFailed || row.rowMeta?.isRowOrderUpdated,
}" }"
> >
@ -2143,21 +2213,22 @@ watch(
<div class="w-[60px] pl-2 pr-1 items-center flex gap-1"> <div class="w-[60px] pl-2 pr-1 items-center flex gap-1">
<div <div
class="nc-row-no sm:min-w-4 text-xs text-gray-500" class="nc-row-no sm:min-w-4 text-xs text-gray-500"
:class="{ toggle: !readOnly, hidden: row.rowMeta?.selected }" :class="{ toggle: !readOnly, hidden: row.rowMeta?.selected || vSelectedAllRecords }"
> >
{{ row.rowMeta.rowIndex + 1 }} {{ row.rowMeta.rowIndex + 1 }}
</div> </div>
<div <div
v-if="!readOnly" v-if="!readOnly"
:class="{ :class="{
hidden: !row.rowMeta?.selected, hidden: !row.rowMeta?.selected && !vSelectedAllRecords,
flex: row.rowMeta?.selected, flex: row.rowMeta?.selected || vSelectedAllRecords,
}" }"
class="nc-row-expand-and-checkbox" class="nc-row-expand-and-checkbox"
> >
<a-checkbox <NcCheckbox
v-model:checked="row.rowMeta.selected" :checked="row.rowMeta.selected || vSelectedAllRecords"
:disabled="!row.rowMeta.selected && selectedRows.length > 100" :disabled="(!row.rowMeta.selected && selectedRows.length > 100) || vSelectedAllRecords"
@change="toggleRowSelection(row.rowMeta.rowIndex)"
/> />
</div> </div>
<span class="flex-1" /> <span class="flex-1" />
@ -2388,31 +2459,45 @@ watch(
<template #overlay> <template #overlay>
<NcMenu class="!rounded !py-0" @click="contextMenu = false"> <NcMenu class="!rounded !py-0" @click="contextMenu = false">
<NcMenuItem <template v-if="!vSelectedAllRecords">
v-if="isEeUI && !contextMenuClosing && !contextMenuTarget && !isDataReadOnly && selectedRows.length" <NcMenuItem
@click="emits('bulkUpdateDlg')" v-if="isEeUI && !contextMenuClosing && !contextMenuTarget && !isDataReadOnly && selectedRows.length"
> @click="emits('bulkUpdateDlg')"
<div v-e="['a:row:update-bulk']" class="flex gap-2 items-center"> >
<component :is="iconMap.ncEdit" /> <div v-e="['a:row:update-bulk']" class="flex gap-2 items-center">
{{ $t('title.updateSelectedRows') }} <component :is="iconMap.ncEdit" />
</div> {{ $t('title.updateSelectedRows') }}
</NcMenuItem> </div>
</NcMenuItem>
<NcMenuItem
v-if="!contextMenuClosing && !contextMenuTarget && !isDataReadOnly && selectedRows.length"
class="nc-base-menu-item !text-red-600 !hover:bg-red-50"
data-testid="nc-delete-row"
@click="deleteSelectedRows"
>
<div v-if="selectedRows.length === 1" v-e="['a:row:delete']" class="flex gap-2 items-center">
<component :is="iconMap.delete" />
{{ $t('activity.deleteSelectedRow') }}
</div>
<div v-else v-e="['a:row:delete-bulk']" class="flex gap-2 items-center">
<component :is="iconMap.delete" />
{{ $t('activity.deleteSelectedRow') }}
</div>
</NcMenuItem>
</template>
<NcMenuItem <NcMenuItem
v-if="!contextMenuClosing && !contextMenuTarget && !isDataReadOnly && selectedRows.length" v-if="vSelectedAllRecords"
class="nc-base-menu-item !text-red-600 !hover:bg-red-50" class="nc-base-menu-item !text-red-600 !hover:bg-red-50"
data-testid="nc-delete-row" data-testid="nc-delete-all-row"
@click="deleteSelectedRows" @click="deleteAllRecords"
> >
<div v-if="selectedRows.length === 1" v-e="['a:row:delete']" class="flex gap-2 items-center"> <div v-e="['a:row:delete-all']" class="flex gap-2 items-center">
<component :is="iconMap.delete" /> <component :is="iconMap.delete" />
{{ $t('activity.deleteSelectedRow') }} {{ $t('activity.deleteAllRecords') }}
</div>
<div v-else v-e="['a:row:delete-bulk']" class="flex gap-2 items-center">
<component :is="iconMap.delete" />
{{ $t('activity.deleteSelectedRow') }}
</div> </div>
</NcMenuItem> </NcMenuItem>
<NcTooltip <NcTooltip
v-if="contextMenuTarget && hasEditPermission && !isDataReadOnly && isSelectedOnlyAI.enabled" v-if="contextMenuTarget && hasEditPermission && !isDataReadOnly && isSelectedOnlyAI.enabled"
:disabled="!isSelectedOnlyAI.disabled" :disabled="!isSelectedOnlyAI.disabled"
@ -2508,7 +2593,7 @@ watch(
</template> </template>
<template v-if="hasEditPermission && !isDataReadOnly"> <template v-if="hasEditPermission && !isDataReadOnly">
<NcDivider v-if="!(!contextMenuClosing && !contextMenuTarget && selectedRows.length)" /> <NcDivider v-if="!(!contextMenuClosing && !contextMenuTarget && (selectedRows.length || vSelectedAllRecords))" />
<NcMenuItem <NcMenuItem
v-if="contextMenuTarget && (selectedRange.isSingleCell() || selectedRange.isSingleRow())" v-if="contextMenuTarget && (selectedRange.isSingleCell() || selectedRange.isSingleRow())"
class="nc-base-menu-item !text-red-600 !hover:bg-red-50" class="nc-base-menu-item !text-red-600 !hover:bg-red-50"

4
packages/nc-gui/components/smartsheet/grid/index.vue

@ -61,6 +61,8 @@ const {
isRowSortRequiredRows, isRowSortRequiredRows,
applySorting, applySorting,
isBulkOperationInProgress, isBulkOperationInProgress,
selectedAllRecords,
bulkDeleteAll,
} = useGridViewData(meta, view, xWhere, reloadVisibleDataHook) } = useGridViewData(meta, view, xWhere, reloadVisibleDataHook)
const rowHeight = computed(() => { const rowHeight = computed(() => {
@ -276,6 +278,7 @@ const {
<InfiniteTable <InfiniteTable
v-else-if="!isGroupBy" v-else-if="!isGroupBy"
ref="tableRef" ref="tableRef"
v-model:selected-all-records="selectedAllRecords"
:load-data="loadData" :load-data="loadData"
:call-add-empty-row="_addEmptyRow" :call-add-empty-row="_addEmptyRow"
:delete-row="deleteRow" :delete-row="deleteRow"
@ -285,6 +288,7 @@ const {
:apply-sorting="applySorting" :apply-sorting="applySorting"
:bulk-update-rows="bulkUpdateRows" :bulk-update-rows="bulkUpdateRows"
:bulk-upsert-rows="bulkUpsertRows" :bulk-upsert-rows="bulkUpsertRows"
:bulk-delete-all="bulkDeleteAll"
:clear-cache="clearCache" :clear-cache="clearCache"
:clear-invalid-rows="clearInvalidRows" :clear-invalid-rows="clearInvalidRows"
:data="cachedRows" :data="cachedRows"

21
packages/nc-gui/composables/useGridViewData.ts

@ -64,7 +64,7 @@ export function useGridViewData(
isFirstRow, isFirstRow,
getExpandedRowIndex, getExpandedRowIndex,
loadData, loadData,
selectedAllRecords,
loadAggCommentsCount, loadAggCommentsCount,
navigateToSiblingRow, navigateToSiblingRow,
} = useInfiniteData({ } = useInfiniteData({
@ -706,6 +706,23 @@ export function useGridViewData(
} }
} }
async function bulkDeleteAll() {
try {
isBulkOperationInProgress.value = true
await $api.dbTableRow.bulkDeleteAll('noco', base.value.id!, meta.value.id!, {
where: where?.value,
viewId: viewMeta.value?.id,
})
} catch (error) {
} finally {
clearCache(Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY)
await syncCount()
syncVisibleData?.()
isBulkOperationInProgress.value = false
}
}
return { return {
cachedRows, cachedRows,
loadData, loadData,
@ -720,6 +737,7 @@ export function useGridViewData(
bulkUpdateRows, bulkUpdateRows,
bulkUpsertRows, bulkUpsertRows,
bulkUpdateView, bulkUpdateView,
bulkDeleteAll,
loadAggCommentsCount, loadAggCommentsCount,
syncCount, syncCount,
removeRowIfNew, removeRowIfNew,
@ -737,5 +755,6 @@ export function useGridViewData(
applySorting, applySorting,
isRowSortRequiredRows, isRowSortRequiredRows,
isBulkOperationInProgress, isBulkOperationInProgress,
selectedAllRecords,
} }
} }

3
packages/nc-gui/composables/useInfiniteData.ts

@ -55,6 +55,8 @@ export function useInfiniteData(args: {
const { nestedFilters, allFilters, xWhere, sorts } = useSmartsheetStoreOrThrow() const { nestedFilters, allFilters, xWhere, sorts } = useSmartsheetStoreOrThrow()
const selectedAllRecords = ref(false)
const routeQuery = computed(() => router.currentRoute.value.query as Record<string, string>) const routeQuery = computed(() => router.currentRoute.value.query as Record<string, string>)
const columnsByAlias = computed(() => { const columnsByAlias = computed(() => {
@ -1142,5 +1144,6 @@ export function useInfiniteData(args: {
getExpandedRowIndex, getExpandedRowIndex,
loadAggCommentsCount, loadAggCommentsCount,
navigateToSiblingRow, navigateToSiblingRow,
selectedAllRecords,
} }
} }

1
packages/nc-gui/lang/en.json

@ -1067,6 +1067,7 @@
"viewHide": "View visibility " "viewHide": "View visibility "
}, },
"activity": { "activity": {
"deleteAllRecords": "Delete all records",
"assignView": "Assign view", "assignView": "Assign view",
"webhookDetails": "Webhook Details", "webhookDetails": "Webhook Details",
"showSaturdaysAndSundays": "Show Saturdays & Sundays", "showSaturdaysAndSundays": "Show Saturdays & Sundays",

1
packages/nocodb/src/controllers/bulk-data-alias.controller.ts

@ -112,6 +112,7 @@ export class BulkDataAliasController {
baseName: baseName, baseName: baseName,
tableName: tableName, tableName: tableName,
query: req.query, query: req.query,
viewName: req.query.viewId,
}); });
} }

16
packages/nocodb/src/schema/swagger.json

@ -11708,22 +11708,6 @@
} }
}, },
"tags": ["DB Table Row"], "tags": ["DB Table Row"],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object"
},
"examples": {
"Example 1": {
"value": {
"Title": "foo"
}
}
}
}
}
},
"description": "Bulk Delete all Table Rows if the condition is true", "description": "Bulk Delete all Table Rows if the condition is true",
"parameters": [ "parameters": [
{ {

Loading…
Cancel
Save