feat: support bulk select and delete for documents (#13907)
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
|||||||
import CardLayout from 'dashboard/components-next/CardLayout.vue';
|
import CardLayout from 'dashboard/components-next/CardLayout.vue';
|
||||||
import DropdownMenu from 'dashboard/components-next/dropdown-menu/DropdownMenu.vue';
|
import DropdownMenu from 'dashboard/components-next/dropdown-menu/DropdownMenu.vue';
|
||||||
import Button from 'dashboard/components-next/button/Button.vue';
|
import Button from 'dashboard/components-next/button/Button.vue';
|
||||||
|
import Checkbox from 'dashboard/components-next/checkbox/Checkbox.vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
id: {
|
id: {
|
||||||
@@ -34,14 +35,34 @@ const props = defineProps({
|
|||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
isSelected: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
selectable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
showSelectionControl: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
showMenu: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['action']);
|
const emit = defineEmits(['action', 'select', 'hover']);
|
||||||
const { checkPermissions } = usePolicy();
|
const { checkPermissions } = usePolicy();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const [showActionsDropdown, toggleDropdown] = useToggle();
|
const [showActionsDropdown, toggleDropdown] = useToggle();
|
||||||
|
const modelValue = computed({
|
||||||
|
get: () => props.isSelected,
|
||||||
|
set: () => emit('select', props.id),
|
||||||
|
});
|
||||||
|
|
||||||
const menuItems = computed(() => {
|
const menuItems = computed(() => {
|
||||||
const allOptions = [
|
const allOptions = [
|
||||||
@@ -79,12 +100,23 @@ const handleAction = ({ action, value }) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<CardLayout>
|
<CardLayout
|
||||||
|
:selectable="selectable"
|
||||||
|
class="relative"
|
||||||
|
@mouseenter="emit('hover', true)"
|
||||||
|
@mouseleave="emit('hover', false)"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-show="showSelectionControl"
|
||||||
|
class="absolute top-7 ltr:left-3 rtl:right-3"
|
||||||
|
>
|
||||||
|
<Checkbox v-model="modelValue" />
|
||||||
|
</div>
|
||||||
<div class="flex gap-1 justify-between w-full">
|
<div class="flex gap-1 justify-between w-full">
|
||||||
<span class="text-base text-n-slate-12 line-clamp-1">
|
<span class="text-base text-n-slate-12 line-clamp-1">
|
||||||
{{ name }}
|
{{ name }}
|
||||||
</span>
|
</span>
|
||||||
<div class="flex gap-2 items-center">
|
<div v-if="showMenu" class="flex gap-2 items-center">
|
||||||
<div
|
<div
|
||||||
v-on-clickaway="() => toggleDropdown(false)"
|
v-on-clickaway="() => toggleDropdown(false)"
|
||||||
class="flex relative items-center group"
|
class="flex relative items-center group"
|
||||||
|
|||||||
@@ -21,16 +21,22 @@ const emit = defineEmits(['deleteSuccess']);
|
|||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
const bulkDeleteDialogRef = ref(null);
|
const bulkDeleteDialogRef = ref(null);
|
||||||
const i18nKey = computed(() => props.type.toUpperCase());
|
const i18nKey = computed(() => {
|
||||||
|
const i18nTypeMap = {
|
||||||
|
AssistantResponse: 'RESPONSES',
|
||||||
|
AssistantDocument: 'DOCUMENTS',
|
||||||
|
};
|
||||||
|
return i18nTypeMap[props.type];
|
||||||
|
});
|
||||||
|
|
||||||
const handleBulkDelete = async ids => {
|
const handleBulkDelete = async ids => {
|
||||||
if (!ids) return;
|
if (!ids) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await store.dispatch(
|
await store.dispatch('captainBulkActions/handleBulkDelete', {
|
||||||
'captainBulkActions/handleBulkDelete',
|
ids: Array.from(props.bulkIds),
|
||||||
Array.from(props.bulkIds)
|
type: props.type,
|
||||||
);
|
});
|
||||||
|
|
||||||
emit('deleteSuccess');
|
emit('deleteSuccess');
|
||||||
useAlert(t(`CAPTAIN.${i18nKey.value}.BULK_DELETE.SUCCESS_MESSAGE`));
|
useAlert(t(`CAPTAIN.${i18nKey.value}.BULK_DELETE.SUCCESS_MESSAGE`));
|
||||||
|
|||||||
@@ -738,6 +738,17 @@
|
|||||||
"DOCUMENTS": {
|
"DOCUMENTS": {
|
||||||
"HEADER": "Documents",
|
"HEADER": "Documents",
|
||||||
"ADD_NEW": "Create a new document",
|
"ADD_NEW": "Create a new document",
|
||||||
|
"SELECTED": "{count} selected",
|
||||||
|
"SELECT_ALL": "Select all ({count})",
|
||||||
|
"UNSELECT_ALL": "Unselect all ({count})",
|
||||||
|
"BULK_DELETE_BUTTON": "Delete",
|
||||||
|
"BULK_DELETE": {
|
||||||
|
"TITLE": "Delete documents?",
|
||||||
|
"DESCRIPTION": "Are you sure you want to delete the selected documents? This action cannot be undone.",
|
||||||
|
"CONFIRM": "Yes, delete all",
|
||||||
|
"SUCCESS_MESSAGE": "Documents deleted successfully",
|
||||||
|
"ERROR_MESSAGE": "There was an error deleting the documents, please try again."
|
||||||
|
},
|
||||||
"RELATED_RESPONSES": {
|
"RELATED_RESPONSES": {
|
||||||
"TITLE": "Related FAQs",
|
"TITLE": "Related FAQs",
|
||||||
"DESCRIPTION": "These FAQs are generated directly from the document."
|
"DESCRIPTION": "These FAQs are generated directly from the document."
|
||||||
|
|||||||
@@ -4,9 +4,13 @@ import { useMapGetter, useStore } from 'dashboard/composables/store';
|
|||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||||
import { useAccount } from 'dashboard/composables/useAccount';
|
import { useAccount } from 'dashboard/composables/useAccount';
|
||||||
|
import { usePolicy } from 'dashboard/composables/usePolicy';
|
||||||
|
|
||||||
import DeleteDialog from 'dashboard/components-next/captain/pageComponents/DeleteDialog.vue';
|
import DeleteDialog from 'dashboard/components-next/captain/pageComponents/DeleteDialog.vue';
|
||||||
import DocumentCard from 'dashboard/components-next/captain/assistant/DocumentCard.vue';
|
import DocumentCard from 'dashboard/components-next/captain/assistant/DocumentCard.vue';
|
||||||
|
import BulkSelectBar from 'dashboard/components-next/captain/assistant/BulkSelectBar.vue';
|
||||||
|
import BulkDeleteDialog from 'dashboard/components-next/captain/pageComponents/BulkDeleteDialog.vue';
|
||||||
|
import Policy from 'dashboard/components/policy.vue';
|
||||||
import PageLayout from 'dashboard/components-next/captain/PageLayout.vue';
|
import PageLayout from 'dashboard/components-next/captain/PageLayout.vue';
|
||||||
import CaptainPaywall from 'dashboard/components-next/captain/pageComponents/Paywall.vue';
|
import CaptainPaywall from 'dashboard/components-next/captain/pageComponents/Paywall.vue';
|
||||||
import RelatedResponses from 'dashboard/components-next/captain/pageComponents/document/RelatedResponses.vue';
|
import RelatedResponses from 'dashboard/components-next/captain/pageComponents/document/RelatedResponses.vue';
|
||||||
@@ -14,9 +18,12 @@ import CreateDocumentDialog from 'dashboard/components-next/captain/pageComponen
|
|||||||
import DocumentPageEmptyState from 'dashboard/components-next/captain/pageComponents/emptyStates/DocumentPageEmptyState.vue';
|
import DocumentPageEmptyState from 'dashboard/components-next/captain/pageComponents/emptyStates/DocumentPageEmptyState.vue';
|
||||||
import FeatureSpotlightPopover from 'dashboard/components-next/feature-spotlight/FeatureSpotlightPopover.vue';
|
import FeatureSpotlightPopover from 'dashboard/components-next/feature-spotlight/FeatureSpotlightPopover.vue';
|
||||||
import LimitBanner from 'dashboard/components-next/captain/pageComponents/document/LimitBanner.vue';
|
import LimitBanner from 'dashboard/components-next/captain/pageComponents/document/LimitBanner.vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { checkPermissions } = usePolicy();
|
||||||
|
|
||||||
const { isOnChatwootCloud } = useAccount();
|
const { isOnChatwootCloud } = useAccount();
|
||||||
const uiFlags = useMapGetter('captainDocuments/getUIFlags');
|
const uiFlags = useMapGetter('captainDocuments/getUIFlags');
|
||||||
@@ -25,9 +32,13 @@ const isFetching = computed(() => uiFlags.value.fetchingList);
|
|||||||
const documentsMeta = useMapGetter('captainDocuments/getMeta');
|
const documentsMeta = useMapGetter('captainDocuments/getMeta');
|
||||||
|
|
||||||
const selectedAssistantId = computed(() => Number(route.params.assistantId));
|
const selectedAssistantId = computed(() => Number(route.params.assistantId));
|
||||||
|
const canManageDocuments = computed(() => checkPermissions(['administrator']));
|
||||||
|
|
||||||
const selectedDocument = ref(null);
|
const selectedDocument = ref(null);
|
||||||
const deleteDocumentDialog = ref(null);
|
const deleteDocumentDialog = ref(null);
|
||||||
|
const bulkDeleteDialog = ref(null);
|
||||||
|
const bulkSelectedIds = ref(new Set());
|
||||||
|
const hoveredCard = ref(null);
|
||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
deleteDocumentDialog.value.dialogRef.open();
|
deleteDocumentDialog.value.dialogRef.open();
|
||||||
@@ -78,7 +89,14 @@ const fetchDocuments = (page = 1) => {
|
|||||||
store.dispatch('captainDocuments/get', filterParams);
|
store.dispatch('captainDocuments/get', filterParams);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPageChange = page => fetchDocuments(page);
|
const onPageChange = page => {
|
||||||
|
const hadSelection = bulkSelectedIds.value.size > 0;
|
||||||
|
fetchDocuments(page);
|
||||||
|
|
||||||
|
if (hadSelection) {
|
||||||
|
bulkSelectedIds.value = new Set();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const onDeleteSuccess = () => {
|
const onDeleteSuccess = () => {
|
||||||
if (documents.value?.length === 0 && documentsMeta.value?.page > 1) {
|
if (documents.value?.length === 0 && documentsMeta.value?.page > 1) {
|
||||||
@@ -86,6 +104,58 @@ const onDeleteSuccess = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const buildSelectedCountLabel = computed(() => {
|
||||||
|
const count = documents.value?.length || 0;
|
||||||
|
const isAllSelected = bulkSelectedIds.value.size === count && count > 0;
|
||||||
|
return isAllSelected
|
||||||
|
? t('CAPTAIN.DOCUMENTS.UNSELECT_ALL', { count })
|
||||||
|
: t('CAPTAIN.DOCUMENTS.SELECT_ALL', { count });
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedCountLabel = computed(() => {
|
||||||
|
return t('CAPTAIN.DOCUMENTS.SELECTED', {
|
||||||
|
count: bulkSelectedIds.value.size,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasBulkSelection = computed(() => bulkSelectedIds.value.size > 0);
|
||||||
|
|
||||||
|
const shouldShowSelectionControl = docId => {
|
||||||
|
return (
|
||||||
|
canManageDocuments.value &&
|
||||||
|
(hoveredCard.value === docId || hasBulkSelection.value)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCardHover = (isHovered, id) => {
|
||||||
|
hoveredCard.value = isHovered ? id : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCardSelect = id => {
|
||||||
|
if (!canManageDocuments.value) return;
|
||||||
|
const selected = new Set(bulkSelectedIds.value);
|
||||||
|
selected[selected.has(id) ? 'delete' : 'add'](id);
|
||||||
|
bulkSelectedIds.value = selected;
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchDocumentsAfterBulkAction = () => {
|
||||||
|
const hasNoDocumentsLeft = documents.value?.length === 0;
|
||||||
|
const currentPage = documentsMeta.value?.page;
|
||||||
|
|
||||||
|
if (hasNoDocumentsLeft) {
|
||||||
|
const pageToFetch = currentPage > 1 ? currentPage - 1 : currentPage;
|
||||||
|
fetchDocuments(pageToFetch);
|
||||||
|
} else {
|
||||||
|
fetchDocuments(currentPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
bulkSelectedIds.value = new Set();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onBulkDeleteSuccess = () => {
|
||||||
|
fetchDocumentsAfterBulkAction();
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchDocuments();
|
fetchDocuments();
|
||||||
});
|
});
|
||||||
@@ -106,6 +176,21 @@ onMounted(() => {
|
|||||||
@update:current-page="onPageChange"
|
@update:current-page="onPageChange"
|
||||||
@click="handleCreateDocument"
|
@click="handleCreateDocument"
|
||||||
>
|
>
|
||||||
|
<template #subHeader>
|
||||||
|
<Policy :permissions="['administrator']">
|
||||||
|
<BulkSelectBar
|
||||||
|
v-model="bulkSelectedIds"
|
||||||
|
:all-items="documents"
|
||||||
|
:select-all-label="buildSelectedCountLabel"
|
||||||
|
:selected-count-label="selectedCountLabel"
|
||||||
|
:delete-label="$t('CAPTAIN.DOCUMENTS.BULK_DELETE_BUTTON')"
|
||||||
|
class="w-fit"
|
||||||
|
:class="{ 'mb-2': bulkSelectedIds.size > 0 }"
|
||||||
|
@bulk-delete="bulkDeleteDialog.dialogRef.open()"
|
||||||
|
/>
|
||||||
|
</Policy>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template #knowMore>
|
<template #knowMore>
|
||||||
<FeatureSpotlightPopover
|
<FeatureSpotlightPopover
|
||||||
:button-label="$t('CAPTAIN.HEADER_KNOW_MORE')"
|
:button-label="$t('CAPTAIN.HEADER_KNOW_MORE')"
|
||||||
@@ -138,7 +223,13 @@ onMounted(() => {
|
|||||||
:external-link="doc.external_link"
|
:external-link="doc.external_link"
|
||||||
:assistant="doc.assistant"
|
:assistant="doc.assistant"
|
||||||
:created-at="doc.created_at"
|
:created-at="doc.created_at"
|
||||||
|
:is-selected="canManageDocuments && bulkSelectedIds.has(doc.id)"
|
||||||
|
:selectable="canManageDocuments"
|
||||||
|
:show-selection-control="shouldShowSelectionControl(doc.id)"
|
||||||
|
:show-menu="!bulkSelectedIds.has(doc.id)"
|
||||||
@action="handleAction"
|
@action="handleAction"
|
||||||
|
@select="handleCardSelect"
|
||||||
|
@hover="isHovered => handleCardHover(isHovered, doc.id)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -162,5 +253,12 @@ onMounted(() => {
|
|||||||
type="Documents"
|
type="Documents"
|
||||||
@delete-success="onDeleteSuccess"
|
@delete-success="onDeleteSuccess"
|
||||||
/>
|
/>
|
||||||
|
<BulkDeleteDialog
|
||||||
|
v-if="bulkSelectedIds"
|
||||||
|
ref="bulkDeleteDialog"
|
||||||
|
:bulk-ids="bulkSelectedIds"
|
||||||
|
type="AssistantDocument"
|
||||||
|
@delete-success="onBulkDeleteSuccess"
|
||||||
|
/>
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -316,7 +316,7 @@ onMounted(() => {
|
|||||||
v-if="bulkSelectedIds"
|
v-if="bulkSelectedIds"
|
||||||
ref="bulkDeleteDialog"
|
ref="bulkDeleteDialog"
|
||||||
:bulk-ids="bulkSelectedIds"
|
:bulk-ids="bulkSelectedIds"
|
||||||
type="Responses"
|
type="AssistantResponse"
|
||||||
@delete-success="onBulkDeleteSuccess"
|
@delete-success="onBulkDeleteSuccess"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -361,7 +361,7 @@ onMounted(() => {
|
|||||||
v-if="bulkSelectedIds"
|
v-if="bulkSelectedIds"
|
||||||
ref="bulkDeleteDialog"
|
ref="bulkDeleteDialog"
|
||||||
:bulk-ids="bulkSelectedIds"
|
:bulk-ids="bulkSelectedIds"
|
||||||
type="Responses"
|
type="AssistantResponse"
|
||||||
@delete-success="onBulkDeleteSuccess"
|
@delete-success="onBulkDeleteSuccess"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -25,17 +25,26 @@ export default createStore({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
handleBulkDelete: async function handleBulkDelete({ dispatch }, ids) {
|
handleBulkDelete: async function handleBulkDelete(
|
||||||
|
{ dispatch },
|
||||||
|
{ type = 'AssistantResponse', ids }
|
||||||
|
) {
|
||||||
const response = await dispatch('processBulkAction', {
|
const response = await dispatch('processBulkAction', {
|
||||||
type: 'AssistantResponse',
|
type,
|
||||||
actionType: 'delete',
|
actionType: 'delete',
|
||||||
ids,
|
ids,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update the response store after successful API call
|
if (type === 'AssistantResponse') {
|
||||||
await dispatch('captainResponses/removeBulkResponses', ids, {
|
// Update the response store after successful API call
|
||||||
root: true,
|
await dispatch('captainResponses/removeBulkResponses', ids, {
|
||||||
});
|
root: true,
|
||||||
|
});
|
||||||
|
} else if (type === 'AssistantDocument') {
|
||||||
|
await dispatch('captainDocuments/removeBulkRecords', ids, {
|
||||||
|
root: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -4,4 +4,12 @@ import { createStore } from '../storeFactory';
|
|||||||
export default createStore({
|
export default createStore({
|
||||||
name: 'CaptainDocument',
|
name: 'CaptainDocument',
|
||||||
API: CaptainDocumentAPI,
|
API: CaptainDocumentAPI,
|
||||||
|
actions: mutations => ({
|
||||||
|
removeBulkRecords({ commit, getters }, ids) {
|
||||||
|
const records = getters.getRecords.filter(
|
||||||
|
record => !ids.includes(record.id)
|
||||||
|
);
|
||||||
|
commit(mutations.SET, records);
|
||||||
|
},
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ class Api::V1::Accounts::Captain::BulkActionsController < Api::V1::Accounts::Bas
|
|||||||
before_action :validate_params
|
before_action :validate_params
|
||||||
before_action :type_matches?
|
before_action :type_matches?
|
||||||
|
|
||||||
MODEL_TYPE = ['AssistantResponse'].freeze
|
MODEL_TYPE = %w[AssistantResponse AssistantDocument].freeze
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@responses = process_bulk_action
|
@responses = process_bulk_action
|
||||||
@@ -28,6 +28,8 @@ class Api::V1::Accounts::Captain::BulkActionsController < Api::V1::Accounts::Bas
|
|||||||
case params[:type]
|
case params[:type]
|
||||||
when 'AssistantResponse'
|
when 'AssistantResponse'
|
||||||
handle_assistant_responses
|
handle_assistant_responses
|
||||||
|
when 'AssistantDocument'
|
||||||
|
handle_documents
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -45,6 +47,16 @@ class Api::V1::Accounts::Captain::BulkActionsController < Api::V1::Accounts::Bas
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_documents
|
||||||
|
return [] unless params[:fields][:status] == 'delete'
|
||||||
|
|
||||||
|
documents = Current.account.captain_documents.where(id: params[:ids])
|
||||||
|
return [] unless documents.exists?
|
||||||
|
|
||||||
|
documents.destroy_all
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
def permitted_params
|
def permitted_params
|
||||||
params.permit(:type, ids: [], fields: [:status])
|
params.permit(:type, ids: [], fields: [:status])
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -14,6 +14,14 @@ RSpec.describe 'Api::V1::Accounts::Captain::BulkActions', type: :request do
|
|||||||
status: 'pending'
|
status: 'pending'
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
let!(:documents) do
|
||||||
|
create_list(
|
||||||
|
:captain_document,
|
||||||
|
2,
|
||||||
|
assistant: assistant,
|
||||||
|
account: account
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def json_response
|
def json_response
|
||||||
JSON.parse(response.body, symbolize_names: true)
|
JSON.parse(response.body, symbolize_names: true)
|
||||||
@@ -98,6 +106,28 @@ RSpec.describe 'Api::V1::Accounts::Captain::BulkActions', type: :request do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when deleting documents' do
|
||||||
|
let(:document_delete_params) do
|
||||||
|
{
|
||||||
|
type: 'AssistantDocument',
|
||||||
|
ids: documents.map(&:id),
|
||||||
|
fields: { status: 'delete' }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'deletes the documents and returns an empty array' do
|
||||||
|
expect do
|
||||||
|
post "/api/v1/accounts/#{account.id}/captain/bulk_actions",
|
||||||
|
params: document_delete_params,
|
||||||
|
headers: admin.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
end.to change(Captain::Document, :count).by(-2)
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
expect(json_response).to eq([])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'with missing parameters' do
|
context 'with missing parameters' do
|
||||||
let(:missing_params) do
|
let(:missing_params) do
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user