import getDefaultViewMeta from './DefaultViewMeta'; const USER_REFERENCE = 'UserReference'; const PAGE = '!P!'; const PAGELIST = '!PL!'; export const formatConstants = { WorkStatus: 'WorkStatus', Integer: 'Integer', WorkLink: 'WorkLink' }; class DataApi { mappedPropertyToOriginalProperty: any; originalPropertyToMappedProperty: any; showRecords: any; constructor(showRecords = true) { this.showRecords = showRecords; this.originalPropertyToMappedProperty = {}; this.mappedPropertyToOriginalProperty = {}; this.setShowRecords = this.setShowRecords.bind(this); this.setPropertyMaps = this.setPropertyMaps.bind(this); this.getMappedProperty = this.getMappedProperty.bind(this); this.getOriginalProperty = this.getOriginalProperty.bind(this); } setShowRecords(showRecords) { this.showRecords = showRecords; } setPropertyMaps(originalToMappedPropertyObj = {}, mappedToOriginalPropertyObj = {}) { this.originalPropertyToMappedProperty = { ...this.originalPropertyToMappedProperty, ...originalToMappedPropertyObj }; this.mappedPropertyToOriginalProperty = { ...this.mappedPropertyToOriginalProperty, ...mappedToOriginalPropertyObj }; } getMappedProperty(propertyName) { return this.originalPropertyToMappedProperty[propertyName] ?? propertyName; } getOriginalProperty(propertyName) { return this.mappedPropertyToOriginalProperty[propertyName] ?? propertyName; } } export async function getContext(componentConfig) { const { promisesArray = [] // array of promises which can be invoked paralelly, } = componentConfig; const promisesResponseArray = await Promise.all(promisesArray); const dataApi = new DataApi(); return { promisesResponseArray, setPropertyMaps: dataApi.setPropertyMaps, getMappedProperty: dataApi.getMappedProperty, getOriginalProperty: dataApi.getOriginalProperty }; } /** * [getFieldNameFromEmbeddedFieldName] * Description - converting embeddedField name starting with !P! or !PL! to normal field * @ignore * @param {string} propertyName EmbeddedField name starting with !P! or !PL! * @returns {string} returns converted string without !P! or !PL! and : * * @example Example for getFieldNameFromEmbeddedFieldName * getFieldNameFromEmbeddedFieldName('!P!Organisation:Name') return 'Organisation.Name' * getFieldNameFromEmbeddedFieldName('!PL!Employees:Name') return 'Employees.Name' */ export function getFieldNameFromEmbeddedFieldName(propertyName) { let value = propertyName; if (value.startsWith(PAGE) || value.startsWith(PAGELIST)) { value = value.substring(value.lastIndexOf('!') + 1); value = value.replace(/:/g, '.'); } return value; } /** * [updateMetaEmbeddedFieldID] * Description - If the fieldID in meta starts with '!P!' or '!PL!' and contains ':' then replace them with .(dot) * @ignore * @param {Array} metaFields Fields metadata Array. Contains metadata of all the fields. */ export function updateMetaEmbeddedFieldID(metaFields) { return metaFields.forEach(metaField => { if (metaField.fieldID?.startsWith(PAGE) || metaField.fieldID?.startsWith(PAGELIST)) { metaField.fieldID = getFieldNameFromEmbeddedFieldName(metaField.fieldID); } }); } export const isEmbeddedField = field => { if (field?.startsWith('@')) { field = field.substring(field.indexOf(' ') + 1); if (field[0] === '.') field = field.substring(1); } return field?.indexOf('.') > 0; }; /** * [isPageListProperty] * Description - checking if propertyName is pageList or not * @ignore * @param {string} propertyName PropertyName * @returns {boolean} true if property is pageList else false * * @example Example for isPageListProperty * isPageListProperty('!PL!Employees.Name') return true * isPageListProperty('!P!Employees.Name') return false * isPageListProperty('Name') return false */ export function isPageListProperty(propertyName) { return propertyName.startsWith(PAGELIST); } export const isPageListInPath = (propertyName, currentClassID) => { if (!propertyName.includes('.')) { return false; } const [first, ...rest] = propertyName.split('.'); const metadata: any = PCore.getMetadataUtils().getPropertyMetadata(first, currentClassID); if (metadata?.type === 'Page List') { return true; } return isPageListInPath(rest.join('.'), metadata?.pageClass); }; /** * [getEmbeddedFieldName] * Description - converting normal field name to embedded field starting with !P! or !PL! * @ignore * @param {string} propertyName Field name * @param {string} classID classID of datapage * @returns {string} returns converted string with !P! or !PL! and : * * @example Example for getEmbeddedFieldName * For page property, getEmbeddedFieldName('Organisation.Name') return '!P!Organisation:Name' * For pageList property, getEmbeddedFieldName('Employees.Name') return '!PL!Employees:Name' */ export function getEmbeddedFieldName(propertyName, classID) { let value = propertyName; if (isPageListInPath(value, classID)) { value = `!PL!${value.replace(/\./g, ':')}`; } else { value = `!P!${value.replace(/\./g, ':')}`; } return value; } /** * [preparePropertyMaps] * Description - preparing maps for property names and set it in dataApi context * @ignore * @param {Array} fields fields array * @param {string} classID classID of datapage * @param {string} context dataApi context * @returns {boolean} true if pageListProperty is present */ export function preparePropertyMaps(fields, classID, context) { const { setPropertyMaps } = context; const maps = fields.reduce( (acc, field) => { let { value } = field.config; if (value.startsWith('@')) { value = value.substring(value.indexOf(' ') + 1); if (value[0] === '.') value = value.substring(1); } let name = value; // Preparing name for embedded property if (isEmbeddedField(name)) { name = getEmbeddedFieldName(name, classID); } if (isPageListProperty(name) && !acc[2]) { acc[2] = true; } acc[0][value] = name; acc[1][name] = value; return acc; }, [{}, {}, false] ); setPropertyMaps(maps[0], maps[1]); return maps[2]; } /** * [getConfigEmbeddedFieldsMeta] * Description - Get the metadata for configured embedded fields * @ignore * @param {Set} configFields Set of config fields * @param {string} classID clasID of datapage * @returns {Array} Metadata of configured embedded fields */ export function getConfigEmbeddedFieldsMeta(configFields, classID) { const configEmbeddedFieldsMeta: any[] = []; configFields.forEach(field => { let value = field; if (isEmbeddedField(value)) { // conversion Page.PageList[].property => Page.PageList.property if (value.includes('[')) { value = value.substring(0, value.indexOf('[')) + value.substring(value.indexOf(']') + 1); } const meta: any = PCore.getMetadataUtils().getEmbeddedPropertyMetadata(value, classID); meta.fieldID = field; configEmbeddedFieldsMeta.push(meta); } }); return configEmbeddedFieldsMeta; } /** * [mergeConfigEmbeddedFieldsMeta] * Description - Get the metadata for configured embedded fields * @ignore * @param {Array} configEmbeddedFieldsMeta config fields metadata. * @param {Array} metaFields Fields metadata Array. Contains metadata of all the fields */ export function mergeConfigEmbeddedFieldsMeta(configEmbeddedFieldsMeta, metaFields) { const mergedMetaFields = [...metaFields]; configEmbeddedFieldsMeta.forEach(configFieldMeta => { const fieldMeta = metaFields.find(metaField => metaField.fieldID === configFieldMeta.fieldID); if (!fieldMeta) mergedMetaFields.push(configFieldMeta); }); return mergedMetaFields; } const oldToNewFieldTypeMapping = { 'Date Time': 'Date & time', Date: 'Date only' }; /** * [updateFieldType] * Description - Updates the field type if its changed in the new implementation. Such mapping is maintained in oldToNewFieldTypeMapping. * @ignore * @param {Array} metaFields Fields metadata Array. Contains metadata of all the fields. */ function updateFieldType(metaFields) { metaFields.forEach(metaField => { if (metaField.type) metaField.type = oldToNewFieldTypeMapping[metaField.type] || metaField.type; }); } function getPresetMetaAttribute(attribute) { const { type, config: { label, value } } = attribute; return { type, name: value.startsWith('@') ? value.substring(4) : value, label: label.startsWith('@') ? label.substring(3) : label }; } /** * [generateViewMetaData] * Description - Returns a list of metaobjects of the fields provided. * @ignore * @param {Array} rawFields List of fields to update meta for * @param {string} classID Class ID from the response * @returns {Array} List of fields with updated meta objects. */ function generateViewMetaData(rawFields, classID, showField) { return rawFields.map(item => getDefaultViewMeta(item, classID, showField)); } /** * [getConfigFields] * Description - Returns list of config fields with primary fields meta updated. * @ignore * @param {Array} configFields List of Authored fields * @param {Array} primaryFields List of Primary Fields * @param {Array} metaFields Metadata of all fields * @param {string} classID Class ID from the response * @returns {Array} List of all fields with their meta updated. */ function getConfigFields(configFields, primaryFields, metaFields, classID) { const presetConfigFields = configFields; const primaryFieldsViewIndex = presetConfigFields.findIndex(field => field.config.value === 'pyPrimaryFields'); if (!primaryFields || !primaryFields.length) { if (primaryFieldsViewIndex < 0) return presetConfigFields; presetConfigFields.splice(primaryFieldsViewIndex, 1); return presetConfigFields; } if (primaryFieldsViewIndex > -1) { // list of uncommon fields - non overlap of primary fields grouped view and independent entity columns of primary type const uncommonFieldsList = primaryFields.filter( primaryField => !presetConfigFields.some(presetConfigField => presetConfigField.config.value.split('.')[1] === primaryField) ); const uncommonFieldsRawMeta: any[] = []; uncommonFieldsList.forEach(uncommonField => { const uncommonFieldMeta = metaFields.find(metaField => metaField.fieldID === uncommonField); if (uncommonFieldMeta) uncommonFieldsRawMeta.push(uncommonFieldMeta); }); const uncommonFieldsConfigMeta = generateViewMetaData(uncommonFieldsRawMeta, classID, true); presetConfigFields.splice(primaryFieldsViewIndex, 1, ...uncommonFieldsConfigMeta); } return presetConfigFields; } /** * [getTableConfigFromPresetMeta] * Description - Get the table config from the presets meta. * @ignore * @param {object} presetMeta Presets meta * @param {boolean} isMetaWithPresets true if meta has presets else false * @param {Function} getPConnect Callback to get the PConnect object * @param {string} classID Class ID from the response * @param {Array} primaryFields List of Primary Fields * @param {Array} metaFields List of all metafields * @returns {object} Table config object */ export function getTableConfigFromPresetMeta(presetMeta, isMetaWithPresets, getPConnect, classID, primaryFields, metaFields) { let presetId; let presetName; let cardHeader; let secondaryText; let timelineDate; let timelineTitle; let timelineStatus; let timelineIcon; let { filterExpression } = getPConnect().getRawMetadata().config; let fieldsMeta; let configFields; if (isMetaWithPresets) { presetId = presetMeta.id; presetName = presetMeta.label; cardHeader = presetMeta.cardHeader && getPresetMetaAttribute(presetMeta.cardHeader); secondaryText = presetMeta.secondaryText && getPresetMetaAttribute(presetMeta.secondaryText); timelineDate = presetMeta.timelineDate && getPresetMetaAttribute(presetMeta.timelineDate); timelineTitle = presetMeta.timelineTitle && getPresetMetaAttribute(presetMeta.timelineTitle); timelineStatus = presetMeta.timelineStatus && getPresetMetaAttribute(presetMeta.timelineStatus); timelineIcon = presetMeta.timelineIcon && getPresetMetaAttribute(presetMeta.timelineIcon); filterExpression = presetMeta.config.filterExpression; [fieldsMeta] = presetMeta.children; if ( presetMeta.timelineTitle && !fieldsMeta.children.find(fieldMeta => { return fieldMeta?.config?.value === presetMeta.timelineTitle?.config?.value; }) ) { const { type, config } = presetMeta.timelineTitle; fieldsMeta.children.push({ type, config: { ...config, show: false } }); } if ( presetMeta.timelineDate && !fieldsMeta.children.find(fieldMeta => { return fieldMeta?.config?.value === presetMeta.timelineDate?.config?.value; }) ) { const { type, config } = presetMeta.timelineDate; fieldsMeta.children.push({ type, config: { ...config, show: false } }); } configFields = getConfigFields(fieldsMeta.children, primaryFields, metaFields, classID); } else { fieldsMeta = presetMeta.props; configFields = getConfigFields( fieldsMeta .getPConnect() .getChildren() ?.map(child => { return child.getPConnect().getRawMetadata(); }), primaryFields, metaFields, classID ); } return { presetId, presetName, cardHeader, secondaryText, timelineDate, timelineTitle, timelineStatus, timelineIcon, filterExpression, fieldsMeta, configFields }; } /** * [getReportColumns] * Description - Returns a set of columns from the report response. * @ignore * @param {object} response - * @returns {Set} Set of columns from the report response */ function getReportColumns(response) { const { data: { data: reportColumns } } = response; const reportColumnsSet = new Set(); reportColumns?.forEach(item => { let val = item.pyFieldName; // Remove '.' from index 0 only, if '.' is present if (val[0] === '.') { val = val.substring(1); } reportColumnsSet.add(val); }); return reportColumnsSet; } /** * [getConfigFieldValue] * Descritpion - Returns a valid value for a configuration field. Remove any annotations and also "." * @ignore * @param {object} config * config.value - Raw value * @returns {string} value - Value with out any annotations or "." */ function getConfigFieldValue(config) { let { value } = config; if (value.startsWith('@')) { value = value.substring(value.indexOf(' ') + 1); if (value[0] === '.') value = value.substring(1); } return value; } /** * [prepareConfigFields] * Description - Prepares a set of configuration fields and pushes each config type to a set using the callback parameter. * @ignore * @param {object} configFields List of Authored fields * @param {Function} pushToComponentsList Callback to push the field type to a set. * @returns {Set} configFieldSet */ function prepareConfigFields(configFields, pushToComponentsList) { const configFieldSet = new Set(); configFields.forEach(item => { pushToComponentsList(item.type); const val = getConfigFieldValue(item.config); configFieldSet.add(val); }); return configFieldSet; } /** * [findAuthoredField] * Description - Finds an authored field from yhe list of config fields. * @ignore * @param {Array} configFields List of Authored fields * @param {string} fieldID Filter * @returns {object} config with its field value equal to fieldID, which means an authored field */ function findAuthoredField(configFields, fieldID) { return configFields.find(configField => { const val = getConfigFieldValue(configField.config); return val === fieldID; }); } /** * [findAndUpdateAuthoredFieldConfig] * Description - Find the authored field, and update its config. * @ignore * @param {Array} configFields List of Authored fields * @param {object} item Field item to copy displayAs and category information * @param {string} classId classID from the response */ function findAndUpdateAuthoredFieldConfig(configFields, item, classId) { const authoredField = findAuthoredField(configFields, item.fieldID); if (authoredField?.config) { if (item.displayAs) { authoredField.config.displayAs = item.displayAs; } authoredField.config.classID = item.classID ?? classId; if (authoredField.type === USER_REFERENCE) { authoredField.config.associationID = item.associationID || item.fieldID; } authoredField.config.category = item.category; // FieldType identifies whether the field is configured as pickList authoredField.config.fieldType = item.fieldType; // type defined on the property rule, // used for the picklist field to assign the appropriate type authoredField.config.propertyType = item.dataType || item.type; } } /** * [isAnExtraField] * Description - Returns true if the field is an extra field. Extra field is the one which is not authored but part of the report. * @ignore * @param {Array} configFields List of Authored fields * @param {Set} configFieldSet Set if Authored filed values * @param {Set} reportColumnsSet Set of columns from the report * @param {object} item Config field item * @param {string} classId Class ID from the response * @param {boolean} showDynamicFields Flag indicating whether fields are fetched dynamically at runtime * @returns {boolean} true If the field is an extra field else false. */ function isAnExtraField(configFields, configFieldSet, reportColumnsSet, item, classId, showDynamicFields) { // Is the field already present in authoring metadata? // Mutates config fields to copy displayAs and category information if (configFieldSet.has(item.fieldID)) { findAndUpdateAuthoredFieldConfig(configFields, item, classId); return false; } // If field is not authored and not part of report columns then discard it return showDynamicFields || !!reportColumnsSet.has(item.fieldID); } /** * [prepareExtraFields] * Description - Returns a list of extra fields with their meta updated. * @ignore * @param {Array} metaFields List of fields * @param {Array} configFields List of Authored fields * @param {Set} configFieldSet Set if Authored filed values * @param {Set} reportColumnsSet Set of columns from the report * @param {string} classID Class ID from the response * @param {boolean} showDynamicFields Flag indicating whether fields are fetched dynamically at runtime * @returns {Array} List of extra fields with their meta updated. */ function prepareExtraFields(metaFields, configFields, configFieldSet, reportColumnsSet, classID, showDynamicFields) { // Filter all the extra fields const extraFileds = metaFields.filter(item => { return isAnExtraField(configFields, configFieldSet, reportColumnsSet, item, classID, showDynamicFields); }); return generateViewMetaData(extraFileds, classID, false); // Update the meta object of each of the extra fields. } const AssignDashObjects = ['Assign-Worklist', 'Assign-WorkBasket']; function isFLProperty(label) { return label?.startsWith('@FL'); } function getFieldLabel(fieldConfig) { const { label, classID, caption } = fieldConfig; let fieldLabel = (label ?? caption)?.substring(4); const labelSplit = fieldLabel?.split('.'); const propertyName = labelSplit?.pop(); const fieldMetaData: any = PCore.getMetadataUtils().getPropertyMetadata(propertyName, classID) ?? {}; fieldLabel = fieldMetaData.label ?? fieldMetaData.caption ?? propertyName; const definedOnClassID = fieldMetaData.definedOnClassID; const localeValue = PCore.getLocaleUtils().getLocaleValue( fieldLabel, `${definedOnClassID ?? fieldMetaData.classID ?? classID}.${propertyName}`, PCore.getLocaleUtils().FIELD_LABELS_BUNDLE_KEY, null ); return localeValue || fieldLabel; } function populateRenderingOptions(name, config, field) { const shouldDisplayAsSemanticLink = 'displayAsLink' in field.config && field.config.displayAsLink; if (shouldDisplayAsSemanticLink) { config.customObject.isAssignmentLink = AssignDashObjects.includes(field.config.classID); if (field.config.value.startsWith('@CA')) { config.customObject.isAssociation = true; } config.cellRenderer = formatConstants.WorkLink; } else if (name === 'pyStatusWork' || name === 'pyAssignmentStatus') { config.cellRenderer = formatConstants.WorkStatus; } else if (name === 'pxUrgencyWork') { config.cellRenderer = formatConstants.Integer; } } export function initializeColumns(fields: any[] = [], getMappedProperty: any = null) { return fields.map((field, originalColIndex) => { let name = field.config.value; if (name.startsWith('@')) { name = name.substring(name.indexOf(' ') + 1); if (name[0] === '.') name = name.substring(1); } name = getMappedProperty?.(name) ?? name; let label = field.config.label || field.config.caption; const { show = true, displayAs } = field.config; if (isFLProperty(label)) { label = getFieldLabel(field.config); } else if (label.startsWith('@')) { label = label.substring(3); } const config = { name, label, show, classID: field.config.classID, id: field.id || name || originalColIndex, displayAs, associationID: field.config.associationID, ...(field.config.classID && { category: field.config.category }), customObject: {}, fieldType: field.config.fieldType, meta: { ...field } }; populateRenderingOptions(name, config, field); return config; }); } export const getItemKey = fields => { let itemKey; if (fields.findIndex(field => field.id === 'pyGUID') > -1) { itemKey = 'pyGUID'; } else { itemKey = 'pzInsKey'; } return itemKey; }; export function preparePatchQueryFields(fields, isDataObject = false, classID = '') { const queryFields: any[] = []; fields.forEach(field => { const patchFields: any[] = []; if (field.cellRenderer === 'WorkLink') { if (field.customObject && field.customObject.isAssignmentLink) { const associationName = field.name.includes(':') ? `${field.name.split(':')[0]}:` : ''; patchFields.push(`${associationName}pzInsKey`); patchFields.push(`${associationName}pxRefObjectClass`); } else if (field.customObject && field.customObject.isAssociation) { const associationCategory = field.name.split(':')[0]; patchFields.push(`${associationCategory}:pyID`); patchFields.push(`${associationCategory}:pzInsKey`); patchFields.push(`${associationCategory}:pxObjClass`); } else if (isDataObject) { const dataViewName = PCore.getDataTypeUtils().getSavableDataPage(classID); const dataPageKeys = PCore.getDataTypeUtils().getDataPageKeys(dataViewName); dataPageKeys?.forEach(item => (item.isAlternateKeyStorage ? patchFields.push(item.linkedField) : patchFields.push(item.keyName))); } else { patchFields.push('pyID'); patchFields.push('pzInsKey'); patchFields.push('pxObjClass'); } } patchFields.forEach(k => { if (!queryFields.find(q => q === k)) { queryFields.push(k); } }); }); return queryFields; } /** * Update the renderer type for the properties of type Page. */ export function updatePageFieldsConfig(configFields, parentClassID) { return configFields.forEach(item => { const { type, config: { value } } = item; const propertyName = PCore.getAnnotationUtils().getPropertyName(value); if (isEmbeddedField(value) && !isPageListInPath(propertyName, parentClassID)) { item.config.componentType = type; item.type = 'PagePropertyRenderer'; } }); } export const readContextResponse = async (context, params) => { const { getPConnect, apiContext, setListContext, children, showDynamicFields, referenceList, isDataObject, ref } = params; const { promisesResponseArray, setShowRecords, apiContext: otherContext } = context; const listOfComponents: any[] = []; ref.setShowRecords = setShowRecords; const { data: { fields: metaFields, classID, isQueryable } } = promisesResponseArray[0]; let { data: { primaryFields } } = promisesResponseArray[0]; // When list is configured with Include all class fields configuration, provide support for Primary fields column if (showDynamicFields) { const sourceMetadata: any = PCore.getMetadataUtils().getDataPageMetadata(referenceList); if (sourceMetadata?.primaryFields) { primaryFields = sourceMetadata.primaryFields; } // updating metaData fieldID to normal property if it has fieldID starts with !P! or !PL! updateMetaEmbeddedFieldID(metaFields); } updateFieldType(metaFields); if (isDataObject) { const compositeKeys: any[] = []; const dataViewName = PCore.getDataTypeUtils().getSavableDataPage(classID); const dataPageKeys = PCore.getDataTypeUtils().getDataPageKeys(dataViewName); dataPageKeys?.forEach(item => (item.isAlternateKeyStorage ? compositeKeys.push(item.linkedField) : compositeKeys.push(item.keyName))); if (compositeKeys.length) { otherContext?.setCompositeKeys(compositeKeys); } if (otherContext) { otherContext.fetchRowActionDetails = null; } } const presetArray = []; const rawMetadata = getPConnect().getRawMetadata().config; const rawPresets = rawMetadata.presets; const isMetaWithPresets = rawPresets && rawPresets.length !== 0; const childrenIterator = isMetaWithPresets ? rawPresets : children; const resolvedPresets = getPConnect().getConfigProps().presets; let fields; let tableConfig; childrenIterator?.forEach((presetMeta, index) => { const { configFields } = getTableConfigFromPresetMeta( { ...presetMeta, label: resolvedPresets[index].label }, isMetaWithPresets, getPConnect, classID, primaryFields, metaFields ); const pushToComponentsList = fieldType => { listOfComponents.push(fieldType); }; // read report columns response - in case of nonqueryable ignore the response and rely only on the fields configured at authoing time in presets const reportColumnsSet = isQueryable ? getReportColumns(promisesResponseArray[1]) : new Set(); const configFieldSet = prepareConfigFields(configFields, pushToComponentsList); // FIXME #EmbeddedPropertyPatch // TODO: Remove this merge logic when the metadata response includes all optimized embedded page and pagelists // merging configured embedded properties with metadata so that while preparing extra fields, config fields can get other properties from meta(eg: category, displayAsLink) // get configured embedded properties metadata and get new mergedMetaFields const configEmbeddedFieldsMeta = getConfigEmbeddedFieldsMeta(configFieldSet, classID); const mergedMetaFields = mergeConfigEmbeddedFieldsMeta(configEmbeddedFieldsMeta, metaFields); const extraFields = prepareExtraFields( mergedMetaFields, // passing new merged meta fields which has meta of configured embedded fields of current preset configFields, configFieldSet, reportColumnsSet, classID, showDynamicFields ); if (isQueryable) { updatePageFieldsConfig(configFields, classID); preparePropertyMaps([...configFields, ...extraFields], classID, context); } const { getMappedProperty } = context; fields = initializeColumns([...configFields, ...extraFields], getMappedProperty); const patchQueryFields = preparePatchQueryFields(fields, isDataObject, classID); const itemKey = getItemKey(fields); tableConfig = { fieldDefs: fields, patchQueryFields, itemKey, isQueryable }; }); const meta = tableConfig; setListContext({ meta, presets: presetArray, apiContext: { ...apiContext } }); };