123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- import * as fs from 'fs'
- import { isObject, get } from 'lodash'
- import { Observable, Subject, interval, map, of } from 'rxjs'
- import { DataPrepService } from './dataprep.service'
- import _ = require("lodash")
- export class SearchService {
- private dataPrepService : DataPrepService
- constructor(){
- this.dataPrepService = new DataPrepService()
- }
- public callFromOtherClass(){
- const t0 = performance.now()
- let i
- for (i = 0; i <= 6000000000; i++) {
- }
- const t1 = performance.now()
- const timeTakenInSeconds = (t1 - t0) / 1000;
- console.log(`Time taken: ${timeTakenInSeconds} seconds to run this function`);
- }
- public query(storageAddress: Storage, ...conditions: Conditions[]): Observable<any> {
- let dataFromStorage: Subject<any> = new Subject()
- let filteredResult: Subject<any> = new Subject()
- this.dataPrepService.loadObsData(storageAddress, dataFromStorage)
- this.filterFromObs(dataFromStorage, filteredResult, ...conditions)
- return filteredResult.pipe()
- }
- // Search and Filter: Pure Observables
- private filterFromObs(dataFromStorage: Subject<any>, filteredResult: Subject<any>, ...conditions: Conditions[]) {
- dataFromStorage.subscribe({
- next: element => {
- if (this.filterByKeyValue(element, ...conditions)) {
- filteredResult.next(element)
- } else {
- console.log(`${element.appData.msgId} does not match search criteria`)
- }
- }
- })
- }
- // Logic 1: Success. But argument must specifies header.messageID.... to search
- private hasMatchingProps(data, condition): boolean {
- // Merge all condtions into searchObj
- let result = _.every(condition, (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 parent property's name. eg: {header.messageID: 1234}
- private filterByKeyValue(data, ...conditions): boolean {
- try {
- // Merge all conditions into searchObj
- let searchObj = Object.assign({}, ...conditions)
- let recordFound = true
- // Check for data type. Can actually remove this code if dont want. Not that important anyways
- if (typeof data !== 'object' || typeof searchObj !== 'object') {
- return false;
- }
- // Check data to see if the given data is within the date range of the specified column
- if (recordFound == true) {
- if (searchObj.hasOwnProperty("$dateRange")) {
- recordFound = this.filterByDateRange(data, searchObj.$dateRange)
- delete searchObj.$dateRange
- }
- }
- // Check if the regular expression value matches any of the data string
- if (recordFound == true) {
- if (searchObj.hasOwnProperty("$regex")) {
- recordFound = this.filterViaRegex(data, searchObj.$regex)
- delete searchObj.$regex
- }
- }
- // Check if the key has parent key notation and then perform matching sequences. Eg : "header.appdata. etc etc"
- if (recordFound == true) {
- // check if key is header.is like 'propertyName1.propertyName2'
- let searchkey = Object.keys(searchObj)
- searchkey.every((key) => {
- if (key.includes('.')) {
- let condition = {
- key: searchObj[key]
- }
- this.hasMatchingProps(data, condition)
- delete searchObj[key]
- }
- })
- }
- // Check the rest of the key value pairs to see if the conditions are fulfilled(entries must matched)
- if (recordFound == true) {
- recordFound = this.matchValues(data, searchObj)
- }
- return recordFound
- }
- catch (e) {
- console.error(e.message)
- }
- }
- // Match the key values pair between conditions and the given data
- private matchValues(data, searchObj): boolean {
- 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 (Array.isArray(searchValue)) {
- // Check if any of the search values are included in the object value
- return searchValue.some((value) => {
- return Array.isArray(objectValue) ? objectValue.includes(value) : objectValue === value;
- });
- } else 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);
- }
- // 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
- private filterViaRegex(element: any, inquiry: any): boolean {
- // create a new regular expression to use regex.test
- const regex = new RegExp(inquiry);
- const hasMatchingSubstring = regex.test(JSON.stringify(element));
- return hasMatchingSubstring;
- }
- // 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
- private filterByDateRange(data: any, dateRange: DateRange): boolean {
- // Lodash implemetation to get the specific property of data
- let msgDate: string = get(data, dateRange.column)
- let date = new Date(msgDate)
- const start = new Date(dateRange.startDate);
- const end = new Date(dateRange.endDate);
- return date >= start && date <= end;
- }
- }
- // Entries that client will use. Subject to be improved later on
- export interface Conditions {
- $regex?: string,
- $dateRange?: DateRange,
- [key: string]: string | Date | DateRange | string[]
- }
- export interface DateRange {
- startDate: string | Date,
- endDate: string | Date,
- column: string
- }
- export interface StorageLocation {
- type: string,
- url: string
- }
- export interface ObservableStorage {
- type: "observable",
- ref: Observable<any>
- }
- export type Storage = StorageLocation| ObservableStorage
|