query.service.ts 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. import { isObject, get } from 'lodash'
  2. import { Observable, Subject, interval, map, of } from 'rxjs'
  3. import { DataPrepService } from './dataprep.service'
  4. import _ = require("lodash")
  5. import { Conditions, DateRange, ObservableStorage, Storage, StorageLocation } from '../types/interface'
  6. export class SearchService {
  7. private dataPrepService: DataPrepService
  8. constructor() {
  9. this.dataPrepService = new DataPrepService()
  10. }
  11. public callFromOtherClass() {
  12. const t0 = performance.now()
  13. let i
  14. for (i = 0; i <= 6000000000; i++) {
  15. }
  16. const t1 = performance.now()
  17. const timeTakenInSeconds = (t1 - t0) / 1000;
  18. console.log(`Time taken: ${timeTakenInSeconds} seconds to run this function`);
  19. }
  20. public query(storage: Storage, ...conditions: Conditions[]): Observable<any> {
  21. let dataFromStorage: Subject<any> = new Subject()
  22. let filteredResult: Subject<any> = new Subject()
  23. if (storage.type == 'observable') {
  24. // ObservableStorage
  25. let currentStorage:ObservableStorage = storage;
  26. let obsRef = currentStorage.ref
  27. obsRef.subscribe((element) => {
  28. dataFromStorage.next(element)
  29. })
  30. } else {
  31. // StorageLocation
  32. let currentStorage:StorageLocation = storage;
  33. this.dataPrepService.loadObsData(currentStorage, dataFromStorage)
  34. }
  35. this.filterFromObs(dataFromStorage, filteredResult, ...conditions)
  36. return filteredResult.pipe()
  37. }
  38. // Search and Filter: Pure Observables
  39. private filterFromObs(dataFromStorage: Subject<any>, filteredResult: Subject<any>, ...conditions: Conditions[]) {
  40. dataFromStorage.subscribe({
  41. next: element => {
  42. if (this.filterByKeyValue(element, ...conditions)) {
  43. filteredResult.next(element)
  44. } else {
  45. console.log(`${element.appData.msgId} does not match search criteria`)
  46. }
  47. }
  48. })
  49. }
  50. // Logic 1: Success. But argument must specifies header.messageID.... to search
  51. private hasMatchingProps(data, condition): boolean {
  52. // Merge all condtions into searchObj
  53. let result = _.every(condition, (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}
  67. // without specifying its parent property's name. eg: {header.messageID: 1234}
  68. private filterByKeyValue(data, ...conditions): boolean {
  69. try {
  70. // Merge all conditions into searchObj
  71. let searchObj = Object.assign({}, ...conditions)
  72. let recordFound = true
  73. // Check for data type. Can actually remove this code if dont want. Not that important anyways
  74. if (typeof data !== 'object' || typeof searchObj !== 'object') {
  75. return false;
  76. }
  77. // Check data to see if the given data is within the date range of the specified column
  78. if (recordFound == true) {
  79. if (searchObj.hasOwnProperty("$dateRange")) {
  80. recordFound = this.filterByDateRange(data, searchObj.$dateRange)
  81. delete searchObj.$dateRange
  82. }
  83. }
  84. // Check if the regular expression value matches any of the data string
  85. if (recordFound == true) {
  86. if (searchObj.hasOwnProperty("$regex")) {
  87. recordFound = this.filterViaRegex(data, searchObj.$regex)
  88. delete searchObj.$regex
  89. }
  90. }
  91. // Check if the key has parent key notation and then perform matching sequences. Eg : "header.appdata. etc etc"
  92. if (recordFound == true) {
  93. // check if key is header.is like 'propertyName1.propertyName2'
  94. let searchkey = Object.keys(searchObj)
  95. searchkey.every((key) => {
  96. if (key.includes('.')) {
  97. let condition = {
  98. key: searchObj[key]
  99. }
  100. this.hasMatchingProps(data, condition)
  101. delete searchObj[key]
  102. }
  103. })
  104. }
  105. // Check the rest of the key value pairs to see if the conditions are fulfilled(entries must matched)
  106. if (recordFound == true) {
  107. recordFound = this.matchValues(data, searchObj)
  108. }
  109. return recordFound
  110. }
  111. catch (e) {
  112. console.error(e.message)
  113. }
  114. }
  115. // Match the key values pair between conditions and the given data
  116. private matchValues(data, searchObj): boolean {
  117. let matchKeys = Object.keys(searchObj);
  118. let isMatchingObject = (object) => {
  119. return matchKeys.every((key) => {
  120. let lodashPath = key.replace(/\[(\w+)\]/g, '.$1').replace(/^\./, '');
  121. let objectValue = _.get(object, lodashPath);
  122. let searchValue = searchObj[key];
  123. if (Array.isArray(searchValue)) {
  124. // Check if any of the search values are included in the object value
  125. return searchValue.some((value) => {
  126. return Array.isArray(objectValue) ? objectValue.includes(value) : objectValue === value;
  127. });
  128. } else if (typeof searchValue === 'object' && typeof objectValue === 'object') {
  129. return isMatchingObject(objectValue);
  130. } else {
  131. return objectValue === searchValue;
  132. }
  133. });
  134. };
  135. let isObjectMatching = (object) => {
  136. if (typeof object !== 'object') {
  137. return false;
  138. }
  139. return isMatchingObject(object) || Object.values(object).some(isObjectMatching);
  140. };
  141. return isObjectMatching(data);
  142. }
  143. // Matching the regex args to see if it matches the data that is now converted to string. As long as partial match, it will return true
  144. private filterViaRegex(element: any, inquiry: any): boolean {
  145. // create a new regular expression to use regex.test
  146. const regex = new RegExp(inquiry);
  147. const hasMatchingSubstring = regex.test(JSON.stringify(element));
  148. return hasMatchingSubstring;
  149. }
  150. // Check if the data's date is within the date range provided and also the column in which the data is to be compared with
  151. private filterByDateRange(data: any, dateRange: DateRange): boolean {
  152. // Lodash implemetation to get the specific property of data
  153. let msgDate: string = get(data, dateRange.column)
  154. let date = new Date(msgDate)
  155. const start = new Date(dateRange.startDate);
  156. const end = new Date(dateRange.endDate);
  157. return date >= start && date <= end;
  158. }
  159. }