forked from Ivasoft/mattermost-mobile
* added MENU_ITEM_HEIGHT to constant/view * fix user presence and your profile * added MENU_ITEM_HEIGHT to constant/view * fix user presence and your profile * UI Polish - Custom Status * UI Polish - Settings * UI Polish - logout * refactored styles * removed 'throws DataOperatorException' from './database/` * fix for copy link option * fix autoresponder 1. user should be allowed to enter paragraph 2. the OOO was not immediately being updated on the notification main screen. The fix is to cal fetchStatusInBatch after the updateMe operation * About Screen - code clean up * removed MenuItem component from common_post_options * removed MenuItem from Settings * refactored show_more and recent_item * removed menu_item component * Update setting_container.tsx * PR review correction * Update setting_container.tsx * Update recent_item.tsx
241 lines
9.1 KiB
TypeScript
241 lines
9.1 KiB
TypeScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import {Database, Q} from '@nozbe/watermelondb';
|
|
|
|
import {
|
|
getRangeOfValues,
|
|
getValidRecordsForUpdate,
|
|
retrieveRecords,
|
|
} from '@database/operator/utils/general';
|
|
import {OperationType} from '@typings/database/enums';
|
|
import {logWarning} from '@utils/log';
|
|
|
|
import type {WriterInterface} from '@nozbe/watermelondb/Database';
|
|
import type Model from '@nozbe/watermelondb/Model';
|
|
import type {
|
|
HandleRecordsArgs,
|
|
OperationArgs,
|
|
ProcessRecordResults,
|
|
ProcessRecordsArgs,
|
|
RecordPair,
|
|
} from '@typings/database/database';
|
|
|
|
export interface BaseDataOperatorType {
|
|
database: Database;
|
|
handleRecords: ({buildKeyRecordBy, fieldName, transformer, createOrUpdateRawValues, deleteRawValues, tableName, prepareRecordsOnly}: HandleRecordsArgs) => Promise<Model[]>;
|
|
processRecords: ({createOrUpdateRawValues, deleteRawValues, tableName, buildKeyRecordBy, fieldName}: ProcessRecordsArgs) => Promise<ProcessRecordResults>;
|
|
batchRecords: (models: Model[]) => Promise<void>;
|
|
prepareRecords: ({tableName, createRaws, deleteRaws, updateRaws, transformer}: OperationArgs) => Promise<Model[]>;
|
|
}
|
|
|
|
export default class BaseDataOperator {
|
|
database: Database;
|
|
|
|
constructor(database: Database) {
|
|
this.database = database;
|
|
}
|
|
|
|
/**
|
|
* processRecords: This method weeds out duplicates entries. It may happen that we do multiple inserts for
|
|
* the same value. Hence, prior to that we query the database and pick only those values that are 'new' from the 'Raw' array.
|
|
* @param {ProcessRecordsArgs} inputsArg
|
|
* @param {RawValue[]} inputsArg.createOrUpdateRawValues
|
|
* @param {string} inputsArg.tableName
|
|
* @param {string} inputsArg.fieldName
|
|
* @param {(existing: Model, newElement: RawValue) => boolean} inputsArg.buildKeyRecordBy
|
|
* @returns {Promise<{ProcessRecordResults}>}
|
|
*/
|
|
processRecords = async ({createOrUpdateRawValues = [], deleteRawValues = [], tableName, buildKeyRecordBy, fieldName}: ProcessRecordsArgs): Promise<ProcessRecordResults> => {
|
|
const getRecords = async (rawValues: RawValue[]) => {
|
|
// We will query a table where one of its fields can match a range of values. Hence, here we are extracting all those potential values.
|
|
const columnValues: string[] = getRangeOfValues({fieldName, raws: rawValues});
|
|
|
|
if (!columnValues.length && rawValues.length) {
|
|
throw new Error(
|
|
`Invalid "fieldName" or "tableName" has been passed to the processRecords method for tableName ${tableName} fieldName ${fieldName}`,
|
|
);
|
|
}
|
|
|
|
if (!rawValues.length) {
|
|
return [];
|
|
}
|
|
|
|
const existingRecords = await retrieveRecords({
|
|
database: this.database,
|
|
tableName,
|
|
condition: Q.where(fieldName, Q.oneOf(columnValues)),
|
|
});
|
|
|
|
return existingRecords;
|
|
};
|
|
|
|
const createRaws: RecordPair[] = [];
|
|
const updateRaws: RecordPair[] = [];
|
|
|
|
// for delete flow
|
|
const deleteRaws = await getRecords(deleteRawValues);
|
|
|
|
// for create or update flow
|
|
const createOrUpdateRaws = await getRecords(createOrUpdateRawValues);
|
|
const recordsByKeys = createOrUpdateRaws.reduce((result: Record<string, Model>, record) => {
|
|
// @ts-expect-error object with string key
|
|
const key = buildKeyRecordBy?.(record) || record[fieldName];
|
|
result[key] = record;
|
|
return result;
|
|
}, {});
|
|
|
|
if (createOrUpdateRawValues.length > 0) {
|
|
for (const newElement of createOrUpdateRawValues) {
|
|
// @ts-expect-error object with string key
|
|
const key = buildKeyRecordBy?.(newElement) || newElement[fieldName];
|
|
const existingRecord = recordsByKeys[key];
|
|
|
|
// We found a record in the database that matches this element; hence, we'll proceed for an UPDATE operation
|
|
if (existingRecord) {
|
|
// Some raw value has an update_at field. We'll proceed to update only if the update_at value is different from the record's value in database
|
|
const updateRecords = getValidRecordsForUpdate({
|
|
tableName,
|
|
existingRecord,
|
|
newValue: newElement,
|
|
});
|
|
|
|
updateRaws.push(updateRecords);
|
|
continue;
|
|
}
|
|
|
|
// This RawValue is not present in the database; hence, we need to create it
|
|
createRaws.push({record: undefined, raw: newElement});
|
|
}
|
|
}
|
|
|
|
return {
|
|
createRaws,
|
|
updateRaws,
|
|
deleteRaws,
|
|
};
|
|
};
|
|
|
|
/**
|
|
* prepareRecords: Utility method that actually calls the operators for the handlers
|
|
* @param {OperationArgs} prepareRecord
|
|
* @param {string} prepareRecord.tableName
|
|
* @param {RawValue[]} prepareRecord.createRaws
|
|
* @param {RawValue[]} prepareRecord.updateRaws
|
|
* @param {Model[]} prepareRecord.deleteRaws
|
|
* @param {(TransformerArgs) => Promise<Model>;} transformer
|
|
* @returns {Promise<Model[]>}
|
|
*/
|
|
prepareRecords = async ({tableName, createRaws, deleteRaws, updateRaws, transformer}: OperationArgs): Promise<Model[]> => {
|
|
if (!this.database) {
|
|
logWarning('Database not defined in prepareRecords');
|
|
return [];
|
|
}
|
|
|
|
let preparedRecords: Array<Promise<Model>> = [];
|
|
|
|
// create operation
|
|
if (createRaws?.length) {
|
|
const recordPromises = createRaws.map(
|
|
(createRecord: RecordPair) => {
|
|
return transformer({
|
|
database: this.database,
|
|
tableName,
|
|
value: createRecord,
|
|
action: OperationType.CREATE,
|
|
});
|
|
},
|
|
);
|
|
|
|
preparedRecords = preparedRecords.concat(recordPromises);
|
|
}
|
|
|
|
// update operation
|
|
if (updateRaws?.length) {
|
|
const recordPromises = updateRaws.map(
|
|
(updateRecord: RecordPair) => {
|
|
return transformer({
|
|
database: this.database,
|
|
tableName,
|
|
value: updateRecord,
|
|
action: OperationType.UPDATE,
|
|
});
|
|
},
|
|
);
|
|
|
|
preparedRecords = preparedRecords.concat(recordPromises);
|
|
}
|
|
|
|
const results = await Promise.all(preparedRecords);
|
|
|
|
if (deleteRaws?.length) {
|
|
deleteRaws.forEach((deleteRecord) => {
|
|
results.push(deleteRecord.prepareDestroyPermanently());
|
|
});
|
|
}
|
|
|
|
return results;
|
|
};
|
|
|
|
/**
|
|
* batchRecords: Accepts an instance of Database (either Default or Server) and an array of
|
|
* prepareCreate/prepareUpdate 'models' and executes the actions on the database.
|
|
* @param {Array} models
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async batchRecords(models: Model[]): Promise<void> {
|
|
try {
|
|
if (models.length > 0) {
|
|
await this.database.write(async (writer: WriterInterface) => {
|
|
await writer.batch(...models);
|
|
});
|
|
}
|
|
} catch (e) {
|
|
logWarning('batchRecords error ', e as Error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* handleRecords : Utility that processes some records' data against values already present in the database so as to avoid duplicity.
|
|
* @param {HandleRecordsArgs} handleRecordsArgs
|
|
* @param {(existing: Model, newElement: RawValue) => boolean} handleRecordsArgs.buildKeyRecordBy
|
|
* @param {string} handleRecordsArgs.fieldName
|
|
* @param {(TransformerArgs) => Promise<Model>} handleRecordsArgs.composer
|
|
* @param {RawValue[]} handleRecordsArgs.createOrUpdateRawValues
|
|
* @param {RawValue[]} handleRecordsArgs.deleteRawValues
|
|
* @param {string} handleRecordsArgs.tableName
|
|
* @returns {Promise<Model[]>}
|
|
*/
|
|
async handleRecords({buildKeyRecordBy, fieldName, transformer, createOrUpdateRawValues, deleteRawValues = [], tableName, prepareRecordsOnly = true}: HandleRecordsArgs): Promise<Model[]> {
|
|
if (!createOrUpdateRawValues.length) {
|
|
logWarning(
|
|
`An empty "rawValues" array has been passed to the handleRecords method for tableName ${tableName}`,
|
|
);
|
|
return [];
|
|
}
|
|
|
|
const {createRaws, deleteRaws, updateRaws} = await this.processRecords({
|
|
createOrUpdateRawValues,
|
|
deleteRawValues,
|
|
tableName,
|
|
buildKeyRecordBy,
|
|
fieldName,
|
|
});
|
|
|
|
let models: Model[] = [];
|
|
models = await this.prepareRecords({
|
|
tableName,
|
|
createRaws,
|
|
updateRaws,
|
|
deleteRaws,
|
|
transformer,
|
|
});
|
|
|
|
if (!prepareRecordsOnly && models?.length) {
|
|
await this.batchRecords(models);
|
|
}
|
|
|
|
return models;
|
|
}
|
|
}
|