|
@@ -1,12 +1,12 @@
|
|
|
import * as fs from 'fs'
|
|
|
-import { filter, isMatch } from 'lodash'
|
|
|
+import { _, isObject } from 'lodash'
|
|
|
import { Observable, Subject, interval, map, of } from 'rxjs'
|
|
|
|
|
|
export class queryService {
|
|
|
private dataFromStorage: Subject<any> = new Subject()
|
|
|
private filteredResult: Subject<any> = new Subject()
|
|
|
|
|
|
- public query(storageAddress: Storage, ...conditions: Entries[]) : Observable<any> {
|
|
|
+ public query(storageAddress: Storage, ...conditions: Entries[]): Observable<any> {
|
|
|
this.loadObsData(storageAddress.address)
|
|
|
this.filterFromObs(...conditions)
|
|
|
return this.filteredResult.pipe()
|
|
@@ -14,6 +14,7 @@ export class queryService {
|
|
|
|
|
|
// Data preparations: Purely Observables
|
|
|
private loadObsData(location: string) {
|
|
|
+ // Temporary version. More defined design will be implemented to cater for different storage locations
|
|
|
let data = fs.readFileSync(location, 'utf-8')
|
|
|
let dataJson = JSON.parse(data)
|
|
|
let count = 0
|
|
@@ -27,20 +28,88 @@ export class queryService {
|
|
|
}, 1000)
|
|
|
}
|
|
|
|
|
|
- // Search and Filter: Pure Observables. To be moved out to become a separate library again.
|
|
|
+ // Search and Filter: Pure Observables. To be moved out to become a separate service again.
|
|
|
private filterFromObs(...conditions: Entries[]) {
|
|
|
this.dataFromStorage.subscribe({
|
|
|
next: element => {
|
|
|
- if(isMatch(element, conditions)){
|
|
|
- // Logic to check if data meets the conditions, if so, put it into result.next{}
|
|
|
+ // Logic to check if data meets the conditions, if so, put it into result.next{}
|
|
|
+ if (this.hasKeyAndValue(element, ...conditions)) {
|
|
|
this.filteredResult.next(element)
|
|
|
+ } else {
|
|
|
+ console.log(`${element.header.messageID} does not match search criteria`)
|
|
|
}
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+ // Logic 1: Success. But argument must specifies header.messageID.... to search
|
|
|
+ private hasMatchingProps(data, ...conditions): boolean {
|
|
|
+ // Merge all condtions into searchObj
|
|
|
+ let searchObj = Object.assign({}, ...conditions)
|
|
|
+ let result = _.every(searchObj, (val, key) => {
|
|
|
+ const propKeys = key.split('.');
|
|
|
+ let nestedObj = data;
|
|
|
+ _.forEach(propKeys, propKey => {
|
|
|
+ nestedObj = nestedObj[propKey];
|
|
|
+ });
|
|
|
+ if (_.isObject(val)) {
|
|
|
+ return this.hasMatchingProps(nestedObj, val);
|
|
|
+ }
|
|
|
+ return nestedObj === val;
|
|
|
+ });
|
|
|
+ return result
|
|
|
+ }
|
|
|
+
|
|
|
+ // Logic 2: Success: More superior version than Logic 1 since it can perform flat searches like {messageID : 1234} without specifying its nested properties
|
|
|
+ private hasKeyAndValue(data, ...conditions): boolean {
|
|
|
+ // Merge all condtions into searchObj
|
|
|
+ let searchObj = Object.assign({}, ...conditions)
|
|
|
+ if (typeof data !== 'object' || typeof searchObj !== 'object') {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ let matchKeys = Object.keys(searchObj);
|
|
|
+ let isMatchingObject = (object) => {
|
|
|
+ return matchKeys.every((key) => {
|
|
|
+ let lodashPath = key.replace(/\[(\w+)\]/g, '.$1').replace(/^\./, '');
|
|
|
+ let objectValue = _.get(object, lodashPath);
|
|
|
+ let searchValue = searchObj[key];
|
|
|
+
|
|
|
+ if (typeof searchValue === 'object' && typeof objectValue === 'object') {
|
|
|
+ return isMatchingObject(objectValue);
|
|
|
+ } else {
|
|
|
+ return objectValue === searchValue;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ let isObjectMatching = (object) => {
|
|
|
+ if (typeof object !== 'object') {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return isMatchingObject(object) || Object.values(object).some(isObjectMatching);
|
|
|
+ };
|
|
|
+
|
|
|
+ return isObjectMatching(data);
|
|
|
+
|
|
|
+ /* This function first merges all the ...conditions objects into a single searchObj object using the Object.assign() method.
|
|
|
+ It then checks whether both data and searchObj are of type object. If either one is not an object, the function returns false.
|
|
|
+ Next, the function defines two helper functions: isMatchingObject and isObjectMatching. isMatchingObject takes an object and
|
|
|
+ returns true if all the key-value pairs in searchObj are present in the object. isObjectMatching takes an object and returns
|
|
|
+ true if the object itself or any of its nested objects satisfy the conditions specified in searchObj.
|
|
|
+ The isMatchingObject function uses the every() method to iterate through each key in searchObj and check if the corresponding
|
|
|
+ value in the object matches the value in searchObj. The function also uses the _.get() method from the Lodash library to get
|
|
|
+ the value of nested object properties using a string path. If the value in searchObj or the object is an object itself,
|
|
|
+ isMatchingObject calls itself recursively to check for nested properties.
|
|
|
+ The isObjectMatching function first checks if the input object is of type object. If not, it returns false. If the object is an object,
|
|
|
+ it checks whether the object or any of its nested objects satisfy the conditions in searchObj by calling isMatchingObject and isObjectMatching recursively.
|
|
|
+ Finally, the hasKeyAndValue function returns the result of isObjectMatching(data), which is a boolean indicating whether data satisfies
|
|
|
+ the conditions specified in searchObj. */
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+
|
|
|
// Entries that client will use. Subject to be improved later on
|
|
|
export interface Entries {
|
|
|
_id?: string,
|
|
@@ -49,7 +118,8 @@ export interface Entries {
|
|
|
msgLogDateTime?: Date | string,
|
|
|
msgDateTime?: Date | string,
|
|
|
msgTag?: string[],
|
|
|
- msgPayload?: string
|
|
|
+ msgPayload?: string,
|
|
|
+ messageID: string
|
|
|
}
|
|
|
|
|
|
export interface Storage {
|