query.service.ts 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import * as fs from 'fs'
  2. import { _, isObject } from 'lodash'
  3. import { Observable, Subject, interval, map, of } from 'rxjs'
  4. export class queryService {
  5. private dataFromStorage: Subject<any> = new Subject()
  6. private filteredResult: Subject<any> = new Subject()
  7. public query(storageAddress: Storage, ...conditions: Conditions[]): Observable<any> {
  8. this.loadObsData(storageAddress.address)
  9. this.filterFromObs(...conditions)
  10. return this.filteredResult.pipe()
  11. }
  12. // Data preparations: Purely Observables
  13. private loadObsData(location: string) {
  14. // Temporary version. More defined design will be implemented to cater for different storage locations
  15. let data = fs.readFileSync(location, 'utf-8')
  16. let dataJson = JSON.parse(data)
  17. let count = 0
  18. const intervalId = setInterval(() => {
  19. this.dataFromStorage.next(dataJson[count]);
  20. count++;
  21. if (count >= 100) {
  22. clearInterval(intervalId);
  23. this.dataFromStorage.complete();
  24. }
  25. }, 250)
  26. }
  27. // Search and Filter: Pure Observables. To be moved out to become a separate service again.
  28. private filterFromObs(...conditions: Conditions[]) {
  29. let enquiry = conditions[0]
  30. let key = Object.keys(enquiry)[0]
  31. this.dataFromStorage.subscribe({
  32. next: element => {
  33. if (key == "regex") {
  34. if (this.filterViaRegex(element, enquiry)) {
  35. this.filteredResult.next(element)
  36. } else {
  37. // console.log(`${element.header.messageName} does not match search criteria`)
  38. }
  39. } else {
  40. if (this.hasKeyAndValue(element, ...conditions)) {
  41. this.filteredResult.next(element)
  42. } else {
  43. // console.log(`${element.header.messageName} does not match search criteria`)
  44. }
  45. }
  46. }
  47. })
  48. }
  49. // Logic 1: Success. But argument must specifies header.messageID.... to search
  50. private hasMatchingProps(data, ...conditions): boolean {
  51. // Merge all condtions into searchObj
  52. let searchObj = Object.assign({}, ...conditions)
  53. let result = _.every(searchObj, (val, key) => {
  54. const propKeys = key.split('.');
  55. let nestedObj = data;
  56. _.forEach(propKeys, propKey => {
  57. nestedObj = nestedObj[propKey];
  58. });
  59. if (_.isObject(val)) {
  60. return this.hasMatchingProps(nestedObj, val);
  61. }
  62. return nestedObj === val;
  63. });
  64. return result
  65. }
  66. // Logic 2: Success: More superior version than Logic 1 since it can perform flat searches like {messageID : 1234} without specifying its nested properties
  67. private hasKeyAndValue(data, ...conditions): boolean {
  68. // Merge all conditions into searchObj
  69. let searchObj = Object.assign({}, ...conditions)
  70. if (typeof data !== 'object' || typeof searchObj !== 'object') {
  71. return false;
  72. }
  73. let matchKeys = Object.keys(searchObj);
  74. let isMatchingObject = (object) => {
  75. return matchKeys.every((key) => {
  76. let lodashPath = key.replace(/\[(\w+)\]/g, '.$1').replace(/^\./, '');
  77. let objectValue = _.get(object, lodashPath);
  78. let searchValue = searchObj[key];
  79. if (Array.isArray(searchValue) && key === 'msgTag') {
  80. // Check if any of the search values are included in the object value
  81. return searchValue.some((value) => {
  82. return Array.isArray(objectValue) ? objectValue.includes(value) : objectValue === value;
  83. });
  84. } else if (typeof searchValue === 'object' && typeof objectValue === 'object') {
  85. return isMatchingObject(objectValue);
  86. } else {
  87. return objectValue === searchValue;
  88. }
  89. });
  90. };
  91. let isObjectMatching = (object) => {
  92. if (typeof object !== 'object') {
  93. return false;
  94. }
  95. return isMatchingObject(object) || Object.values(object).some(isObjectMatching);
  96. };
  97. return isObjectMatching(data);
  98. /* This function first merges all the ...conditions objects into a single searchObj object using the Object.assign() method.
  99. It then checks whether both data and searchObj are of type object. If either one is not an object, the function returns false.
  100. Next, the function defines two helper functions: isMatchingObject and isObjectMatching. isMatchingObject takes an object and
  101. returns true if all the key-value pairs in searchObj are present in the object. isObjectMatching takes an object and returns
  102. true if the object itself or any of its nested objects satisfy the conditions specified in searchObj.
  103. The isMatchingObject function uses the every() method to iterate through each key in searchObj and check if the corresponding
  104. value in the object matches the value in searchObj. The function also uses the _.get() method from the Lodash library to get
  105. the value of nested object properties using a string path. If the value in searchObj or the object is an object itself,
  106. isMatchingObject calls itself recursively to check for nested properties.
  107. The isObjectMatching function first checks if the input object is of type object. If not, it returns false. If the object is an object,
  108. it checks whether the object or any of its nested objects satisfy the conditions in searchObj by calling isMatchingObject and isObjectMatching recursively.
  109. Finally, the hasKeyAndValue function returns the result of isObjectMatching(data), which is a boolean indicating whether data satisfies
  110. the conditions specified in searchObj.
  111. PS: this function is not my code. */
  112. }
  113. private filterViaRegex(element: any, inquiry: any): boolean {
  114. // create a new regular expression to use regex.test
  115. const regex = new RegExp(inquiry.regex);
  116. const hasMatchingSubstring = regex.test(JSON.stringify(element));
  117. return hasMatchingSubstring;
  118. }
  119. }
  120. // Entries that client will use. Subject to be improved later on
  121. export interface Conditions {
  122. _id?: string,
  123. appLogLocId?: string,
  124. msgId?: string,
  125. msgLogDateTime?: Date | string,
  126. msgDateTime?: Date | string,
  127. msgTag?: string[],
  128. msgPayload?: string,
  129. messageID?: string,
  130. regex?: string
  131. }
  132. export interface Storage {
  133. type: string,
  134. address: string
  135. }