Batch Operations with Records¶
Warning
Batch operations are not part of business processes and are run directly.

Figure 1. Drop-down list of actions when selecting multiple records and the "Delete records" button
UEBulkOperationSettingsStore is designed for mass actions on records.
Types for Batch Operations¶
A basic tool for implementing mass operations - BulkOperationStore.
The main tasks of the extension point:
Get a list of operation descriptors (so that the user can select an operation).
Initiate the selected operation for configuration, create a storage for it. The history of a specific operation is the point of extension of the user by his identifier of the type UEBulkOperationSettingsStore.
Starting the operation with the context received from the repository.
An example of working with this storage: the Operations component in the "Data" section is a drop-down list of actions when selecting multiple records.
UEBulkOperationSettingsStore example:
import {UeModuleBase} from '../../userexit/type/UeModuleBase';
import {IBulkOperationSettings} from '../IBulkOperationSettings';
import {ClassCtor} from '@unidata/core';
interface IBulkOperationSettings {
/**
* Steps for wizard with operation settings
*/
readonly wizardSteps: WizardStep[];
/**
* Common bulk operation store (with execute method)
*/
readonly bulkOperationStore: BulkOperationStore;
/**
* Method for get content for execute operation
*/
getContent(): Promise<any>; // payload for BE operation (depends on operations)
}
export type UEBulkOperationSettingsStore = UeModuleBase & {
'default': {
fn: () => ClassCtor<IBulkOperationSettings>;
};
}
Batch Operation of Deleting Records¶
The "Delete records" button appears when you select the necessary records in the search results table.
DeleteRecordsStore is responsible for the bulk operation of deleting records - a user output of the type UEBulkOperationSettingsStore (see above).
Realisation:
import {Res, ResourceManager, Right, UEBulkOperationSettingsStore} from '@unidata/core-app';
import {DeleteRecordsStore} from './DeleteRecordsStore';
import {UEList} from '@unidata/types';
import {dataRecordBulkPayload} from '@universe-se/data/src/utils/dataRecordBulkPayload'; // util function which could get DataRecordBulkPayload
export const UEDeleteRecordBulkOpStore: UEBulkOperationSettingsStore = {
'default': {
type: UEList.BulkOperationSettingsStore,
moduleId: 'com.unidata.mdm.bulk.remove.records[operation]',
active: true,
system: false,
meta: {},
resolver: () => {
return ResourceManager.userHasRight(Res.DATA_OPERATIONS_MANAGEMENT, Right.DELETE);
},
fn: () => {
return DeleteRecordsStore;
}
}
};
Batch Record Modification Operation¶
The "Modify records" button appears when you select the necessary records in the search results table.
ModifyRecordsOperationStore is responsible for a massive record modification operation - a custom output of the type UEBulkOperationSettingsStore (see above).
Realisation:
export const UEModifyRecordsBulkOpStore: UEBulkOperationSettingsStore = {
'default': {
type: UEList.BulkOperationSettingsStore,
moduleId: 'com.universe.mdm.bulk.modify.records[operation]',
active: true,
system: false,
meta: {},
resolver: (searchStore: AbstractSearchStore<AbstractSearchPanelStore, AbstractSearchColumnsStore>) => {
if (!(searchStore instanceof DataSearchStore)) {
return false;
}
const {typeName} = searchStore;
return ResourceManager.userHasRight(
ResourceUtil.stringifyResourceId(ResourceCategory.DATA, typeName),
Right.UPDATE
);
},
fn: () => {
return ModifyRecordsOperationStore;
}
}
};
Batch Operation Without Server Involvement¶
UESearchPageOperationMenuItem provides the ability to add additional functions to the menu of mass actions on records that do not require server tasks.
UESearchPageOperationMenuItem
export type SearchPageMenuItemArgs = {
routerStore: RouterStore;
searchStore: AbstractSearchStore<
AbstractSearchPanelStore,
AbstractSearchColumnsStore
>;
};
export type UESearchPageOperationMenuItem = UeModuleBase & {
default: {
fn: (args: SearchPageMenuItemArgs) => void;
resolver: (args: SearchPageMenuItemArgs) => boolean;
meta: {
isActive?: (args: SearchPageMenuItemArgs) => boolean;
getTitle: (args: SearchPageMenuItemArgs) => string;
};
};
};
Implementation example of the record comparison button
export const compareMenuItem: UESearchPageOperationMenuItem = {
'default': {
type: UEList.SearchPageOperationMenuItem,
resolver: ({searchStore}: SearchPageMenuItemArgs) => {
if (!(searchStore instanceof DataSearchStore)) {
return false;
}
const {checkedSearchHits} = searchStore.searchResult;
const hasDataRight = MetaDataRightUtils.userHasDataRight(searchStore.typeName, Right.READ);
const checkedCount = checkedSearchHits.size;
return checkedCount > 1 && hasDataRight; // show only if checked more the 1 record and user has read rights to selected Entity
},
active: true,
system: false,
moduleId: 'search_page_compare_menu_item',
fn: MenuItemCompare.openCompareDrawer, // function to open modal window with CompareTable or some another route
meta: {
getTitle: () => 'Compare Records'
}
}
};
Extension of Record Batch Modification¶
UEModifyRecords is intended for adding a fragment to the request of the operation of mass modification of records and output in the wizard of this operation of the interface for changing the parameters passed in the fragment
Types:
interface IModifyInnerStore {
/*
* Added to wizard operations 1+ tab/and, parameter component will be rendered in it/they
*/
tabs: TabItem[];
/*
* Returns a fragment of the request
*/
getContent: () => {};
/*
* True, if the parameters specified in the fragment allow the operation to be launched
*/
allowConfirm: boolean;
/*
* True, if the parameters specified in the fragment prohibit the operation to be launched
*/
hasErrors: boolean;
/*
* key under which the fragment will be added to the query
*/
payloadKey: string;
}
type UEModifyRecordsResolver = (modifyOperationStore: ModifyRecordsOperationStore) => boolean;
type UEModifyRecordsMeta = {
/*
* @param key - meta.key of the current User Exit is passed
*/
getStore: (modifyOperationStore: ModifyRecordsOperationStore, key: string) => IModifyInnerStore;
key: string;
};
type UEModifyRecords = UeModuleBase<UEModifyRecordsResolver, UEModifyRecordsMeta> & {
component: ComponentType<{modifyStore: ModifyRecordsOperationStore}>;
};
UEModifyRecords example:
type Props = {
modifyStore: ModifyRecordsOperationStore
}
class ModifyRecordAdditionalStore implements IModifyInnerStore {
public readonly payloadKey: string;
@observable
public modificationProperty1: boolean = false;
@observable
public modificationProperty2: string = '';
@observable
public modificationProperty3: number = 0;
constructor (payloadKey: string) {
this.payloadKey = payloadKey;
}
@computed
get allowConfirm () {
return this.modificationProperty2 !== '';
}
@computed
get hasErrors () {
return this.modificationProperty1 && this.modificationProperty3 === 0;
}
get tabs () {
return [
{
key: this.payloadKey,
tab: 'Дополнительные параметры'
}
];
}
@action
setFieldValue = (name: 'modificationProperty1' | 'modificationProperty2' | 'modificationProperty3', value: any) => {
this[name] = value as
typeof name extends 'modificationProperty1' ? string :
typeof name extends 'modificationProperty2' ? number :
typeof name extends 'modificationProperty3' ? boolean :
never;
}
getContent(): {} {
const {
modificationProperty1,
modificationProperty2,
modificationProperty3
} = this;
return {
modificationProperty1,
modificationProperty2,
modificationProperty3
};
}
}
@observer
class CloneRecordUserExit extends React.Component<Props> {
get store () {
const {modifyStore} = this.props;
return modifyStore.getStore(PAYLOAD_KEY) as ModifyRecordAdditionalStore;
}
override render () {
const {store} = this;
return (
<>
<Field.Checkbox
label={'Параметр 1'}
name={'modificationProperty1'}
defaultChecked={store.modificationProperty1}
onChange={store.setFieldValue}
/>
<Field.Input
label={'Параметр 2'}
name={'modificationProperty2'}
type={'text'}
disabled={false}
readOnly={false}
defaultValue={store.modificationProperty2}
onChange={store.setFieldValue}
/>
<Field.Input
label={'Параметр 3'}
name={'modificationProperty3'}
type={'number'}
disabled={false}
readOnly={false}
defaultValue={store.modificationProperty3}
onChange={store.setFieldValue}
/>
</>
);
}
}
const PAYLOAD_KEY = 'modify-records-additional-fragment-v1';
ueModuleManager.addModule('ModifyRecords', {
active: true,
component: CloneRecordUserExit,
meta: {
getStore: () => {
return new ModifyRecordAdditionalStore(PAYLOAD_KEY)
},
key: PAYLOAD_KEY
},
moduleId: PAYLOAD_KEY,
resolver: (modifyOperationStore: ModifyRecordsOperationStore) => {
const dataRecordInnerStore = modifyOperationStore.dataCardStore.getInnerStore('data-record-additional-store');
return dataRecordInnerStore !== undefined;
},
system: false
});