Browse Source

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

fix: delete all records
pull/9988/head
Anbarasu 6 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 },
undo?: boolean,
) => Promise<void>
bulkDeleteAll?: () => Promise<void>
bulkUpsertRows?: (
insertRows: Row[],
updateRows: [],
@ -57,9 +58,12 @@ const props = defineProps<{
selectedRows: Array<Row>
chunkStates: Array<'loading' | 'loaded' | undefined>
isBulkOperationInProgress: boolean
selectedAllRecords?: boolean
}>()
const emits = defineEmits(['bulkUpdateDlg'])
const emits = defineEmits(['bulkUpdateDlg', 'update:selectedAllRecords'])
const vSelectedAllRecords = useVModel(props, 'selectedAllRecords', emits)
const {
loadData,
@ -75,6 +79,7 @@ const {
removeRowIfNew,
clearInvalidRows,
applySorting,
bulkDeleteAll,
} = props
// 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)
async function expandRows({
newRows,
@ -677,7 +705,7 @@ async function expandRows({
continue: false,
expand: true,
}
const { close } = useDialog(resolveComponent('DlgExpandTable'), {
const { close } = useDialog(resolveComponent('DlgRecordUpsert'), {
'modelValue': isOpen,
'newRows': newRows,
'newColumns': newColumns,
@ -1746,6 +1774,26 @@ watch(
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>
<template>
@ -1778,7 +1826,7 @@ watch(
<div ref="gridWrapper" class="nc-grid-wrapper min-h-0 flex-1 relative !overflow-auto">
<NcDropdown
v-model:visible="contextMenu"
:disabled="contextMenuTarget === null && !selectedRows.length"
:disabled="contextMenuTarget === null && !selectedRows.length && !vSelectedAllRecords"
:trigger="isSqlView ? [] : ['contextmenu']"
overlay-class-name="nc-dropdown-grid-context-menu"
>
@ -1825,7 +1873,29 @@ watch(
}"
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
v-if="fields[0] && fields[0].id"
@ -2127,7 +2197,7 @@ watch(
'active-row':
activeCell.row === row.rowMeta.rowIndex || selectedRange._start?.row === row.rowMeta.rowIndex,
'mouse-down': isGridCellMouseDown || isFillMode,
'selected-row': row.rowMeta.selected,
'selected-row': row.rowMeta.selected || vSelectedAllRecords,
'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="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 }}
</div>
<div
v-if="!readOnly"
:class="{
hidden: !row.rowMeta?.selected,
flex: row.rowMeta?.selected,
hidden: !row.rowMeta?.selected && !vSelectedAllRecords,
flex: row.rowMeta?.selected || vSelectedAllRecords,
}"
class="nc-row-expand-and-checkbox"
>
<a-checkbox
v-model:checked="row.rowMeta.selected"
:disabled="!row.rowMeta.selected && selectedRows.length > 100"
<NcCheckbox
:checked="row.rowMeta.selected || vSelectedAllRecords"
:disabled="(!row.rowMeta.selected && selectedRows.length > 100) || vSelectedAllRecords"
@change="toggleRowSelection(row.rowMeta.rowIndex)"
/>
</div>
<span class="flex-1" />
@ -2388,31 +2459,45 @@ watch(
<template #overlay>
<NcMenu class="!rounded !py-0" @click="contextMenu = false">
<NcMenuItem
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" />
{{ $t('title.updateSelectedRows') }}
</div>
</NcMenuItem>
<template v-if="!vSelectedAllRecords">
<NcMenuItem
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" />
{{ $t('title.updateSelectedRows') }}
</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
v-if="!contextMenuClosing && !contextMenuTarget && !isDataReadOnly && selectedRows.length"
v-if="vSelectedAllRecords"
class="nc-base-menu-item !text-red-600 !hover:bg-red-50"
data-testid="nc-delete-row"
@click="deleteSelectedRows"
data-testid="nc-delete-all-row"
@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" />
{{ $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') }}
{{ $t('activity.deleteAllRecords') }}
</div>
</NcMenuItem>
<NcTooltip
v-if="contextMenuTarget && hasEditPermission && !isDataReadOnly && isSelectedOnlyAI.enabled"
:disabled="!isSelectedOnlyAI.disabled"
@ -2508,7 +2593,7 @@ watch(
</template>
<template v-if="hasEditPermission && !isDataReadOnly">
<NcDivider v-if="!(!contextMenuClosing && !contextMenuTarget && selectedRows.length)" />
<NcDivider v-if="!(!contextMenuClosing && !contextMenuTarget && (selectedRows.length || vSelectedAllRecords))" />
<NcMenuItem
v-if="contextMenuTarget && (selectedRange.isSingleCell() || selectedRange.isSingleRow())"
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,
applySorting,
isBulkOperationInProgress,
selectedAllRecords,
bulkDeleteAll,
} = useGridViewData(meta, view, xWhere, reloadVisibleDataHook)
const rowHeight = computed(() => {
@ -276,6 +278,7 @@ const {
<InfiniteTable
v-else-if="!isGroupBy"
ref="tableRef"
v-model:selected-all-records="selectedAllRecords"
:load-data="loadData"
:call-add-empty-row="_addEmptyRow"
:delete-row="deleteRow"
@ -285,6 +288,7 @@ const {
:apply-sorting="applySorting"
:bulk-update-rows="bulkUpdateRows"
:bulk-upsert-rows="bulkUpsertRows"
:bulk-delete-all="bulkDeleteAll"
:clear-cache="clearCache"
:clear-invalid-rows="clearInvalidRows"
:data="cachedRows"

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

@ -64,7 +64,7 @@ export function useGridViewData(
isFirstRow,
getExpandedRowIndex,
loadData,
selectedAllRecords,
loadAggCommentsCount,
navigateToSiblingRow,
} = 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 {
cachedRows,
loadData,
@ -720,6 +737,7 @@ export function useGridViewData(
bulkUpdateRows,
bulkUpsertRows,
bulkUpdateView,
bulkDeleteAll,
loadAggCommentsCount,
syncCount,
removeRowIfNew,
@ -737,5 +755,6 @@ export function useGridViewData(
applySorting,
isRowSortRequiredRows,
isBulkOperationInProgress,
selectedAllRecords,
}
}

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

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

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

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

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

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

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

@ -11708,22 +11708,6 @@
}
},
"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",
"parameters": [
{

Loading…
Cancel
Save