Skip to content

Commit ce467f9

Browse files
committed
feat: add ability to process records, which are filtered
1 parent b8ef41d commit ce467f9

3 files changed

Lines changed: 90 additions & 6 deletions

File tree

custom/VisionAction.vue

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
:click-to-close-outside="false"
6868
>
6969
<div class="bulk-vision-table flex flex-col items-center gap-3 md:gap-4 overflow-y-auto">
70-
<template v-if="globalState.records.length && props.checkboxes.length && popupMode === 'generation'" >
70+
<template v-if="globalState.records.length && popupMode === 'generation'" >
7171
<VisionTable
7272
class="md:max-h-[75vh] max-w-[1560px] w-full h-full"
7373
:records="globalState.records"
@@ -152,9 +152,11 @@ import { AdminUser, type AdminForthResourceCommon } from '@/types/Common';
152152
import { useCoreStore } from '@/stores/core';
153153
import { IconShieldSolid, IconInfoCircleSolid } from '@iconify-prerendered/vue-flowbite';
154154
import { IconExclamationTriangle } from '@iconify-prerendered/vue-humbleicons';
155+
import { useFiltersStore } from '@/stores/filters';
155156
156157
157158
const coreStore = useCoreStore();
159+
const filtersStore = useFiltersStore();
158160
159161
const { t } = useI18n();
160162
const props = defineProps<{
@@ -232,7 +234,7 @@ const openDialog = async () => {
232234
isDialogOpen.value = true;
233235
confirmDialog.value.open();
234236
isFetchingRecords.value = true;
235-
initializeGlobalState();
237+
await initializeGlobalState();
236238
await findPreviewURLForImages();
237239
isFetchingRecords.value = false;
238240
if (!generationPrompts.value || Object.keys(generationPrompts.value).length === 0) {
@@ -242,6 +244,35 @@ const openDialog = async () => {
242244
runAiActions();
243245
}
244246
}
247+
248+
async function getListOfIds() {
249+
if ( props.meta.recordSelector === 'filtered' ) {
250+
const filters = filtersStore.getFilters(props.resource.resourceId);
251+
let res;
252+
try {
253+
res = await callAdminForthApi({
254+
path: `/plugin/${props.meta.pluginInstanceId}/get_filtered_ids`,
255+
method: 'POST',
256+
body: { filters },
257+
silentError: true,
258+
});
259+
} catch (e) {
260+
console.error('Failed to get records for filtered selector:', e);
261+
isError.value = true;
262+
errorMessage.value = t(`Failed to fetch records. Please, try to re-run the action.`);
263+
return [];
264+
}
265+
if (!res?.ok || !res?.recordIds) {
266+
console.error('Failed to get records for filtered selector, response error:', res);
267+
isError.value = true;
268+
errorMessage.value = t(`Failed to fetch records. Please, try to re-run the action.`);
269+
return [];
270+
}
271+
return res.recordIds;
272+
} else {
273+
return props.checkboxes;
274+
}
275+
}
245276
246277
247278
async function runAiActions() {
@@ -267,8 +298,9 @@ const closeDialog = () => {
267298
isDataSaved.value = false;
268299
}
269300
270-
function initializeGlobalState() {
271-
const records = props.checkboxes.map((recordId: any) => createEmptyRecord(recordId));
301+
async function initializeGlobalState() {
302+
const ids = await getListOfIds();
303+
const records = ids.map((recordId: any) => createEmptyRecord(recordId));
272304
globalState.records.splice(0, globalState.records.length, ...records);
273305
globalState.recordsById = Object.fromEntries(records.map(record => [String(record.id), record]));
274306
}

index.ts

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AdminForthPlugin, Filters } from "adminforth";
1+
import { AdminForthFilterOperators, AdminForthPlugin, Filters } from "adminforth";
22
import type { IAdminForth, IHttpServer, AdminForthComponentDeclaration, AdminForthResource } from "adminforth";
33
import { suggestIfTypo } from "adminforth";
44
import type { PluginOptions } from './types.js';
@@ -601,7 +601,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
601601
isFieldsForAnalizePlain: this.options.fillPlainFields ? Object.keys(this.options.fillPlainFields).length > 0 : false,
602602
isImageGeneration: this.options.generateImages ? Object.keys(this.options.generateImages).length > 0 : false,
603603
isAttachFiles: this.options.attachFiles ? true : false,
604-
disabledWhenNoCheckboxes: true,
604+
disabledWhenNoCheckboxes: this.options.recordSelector === 'filtered' ? false : true,
605605
refreshRates: {
606606
fillFieldsFromImages: this.options.refreshRates?.fillFieldsFromImages || 2_000,
607607
fillPlainFields: this.options.refreshRates?.fillPlainFields || 1_000,
@@ -610,6 +610,7 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
610610
},
611611
askConfirmationBeforeGenerating: this.options.askConfirmationBeforeGenerating || false,
612612
concurrencyLimit: this.options.concurrencyLimit || 10,
613+
recordSelector: this.options.recordSelector || 'checkbox',
613614
generationPrompts: {
614615
plainFieldsPrompts: this.options.fillPlainFields || {},
615616
imageFieldsPrompts: this.options.fillFieldsFromImages || {},
@@ -1034,5 +1035,44 @@ export default class BulkAiFlowPlugin extends AdminForthPlugin {
10341035
}
10351036
}
10361037
});
1038+
1039+
server.endpoint({
1040+
method: 'POST',
1041+
path: `/plugin/${this.pluginInstanceId}/get_filtered_ids`,
1042+
handler: async ({ body, adminUser, headers }) => {
1043+
const filters = body.filters;
1044+
1045+
const normalizedFilters = { operator: AdminForthFilterOperators.AND, subFilters: [] };
1046+
if (filters) {
1047+
if (typeof filters !== 'object') {
1048+
throw new Error(`Filter should be an array or an object`);
1049+
}
1050+
if (Array.isArray(filters)) {
1051+
// if filters are an array, they will be connected with "AND" operator by default
1052+
normalizedFilters.subFilters = filters;
1053+
} else if (filters.field) {
1054+
// assume filter is a SingleFilter
1055+
normalizedFilters.subFilters = [filters];
1056+
} else if (filters.subFilters) {
1057+
// assume filter is a AndOr filter
1058+
normalizedFilters.operator = filters.operator;
1059+
normalizedFilters.subFilters = filters.subFilters;
1060+
} else {
1061+
// wrong filter
1062+
throw new Error(`Wrong filter object value: ${JSON.stringify(filters)}`);
1063+
}
1064+
}
1065+
1066+
const records = await this.adminforth.resource(this.resourceConfig.resourceId).list(normalizedFilters);
1067+
if (!records) {
1068+
return { ok: true, recordIds: [] };
1069+
}
1070+
const primaryKeyColumn = this.resourceConfig.columns.find((col) => col.primaryKey);
1071+
1072+
const recordIds = records.map(record => record[primaryKeyColumn.name]);
1073+
1074+
return { ok: true, recordIds }
1075+
}
1076+
});
10371077
}
10381078
}

types.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,16 @@ export interface PluginOptions {
124124
* Maximum number of records processed concurrently on the frontend.
125125
*/
126126
concurrencyLimit?: number;
127+
128+
/**
129+
* Defines the way how records are selected for the action.
130+
*
131+
* 'checkbox' means that user will select records manually by checkboxes,
132+
*
133+
* 'filtered' means that action will be applied to all records matching current
134+
* filters without showing any checkboxes (use with caution).
135+
*
136+
* Default is 'checkbox'.
137+
*/
138+
recordSelector?: 'checkbox' | 'filtered';
127139
}

0 commit comments

Comments
 (0)