import { parse , stringify } from 'query-string' ; import React , { createContext , useCallback , useContext , useMemo , FC } from 'react' ; import { isEqual } from 'lodash' ; import { decode , encode } from 'rison-node' ; import { useHistory , useLocation } from 'react-router-dom' ; import { Dictionary } from '../../../common/types/common' ; import { getNestedProperty } from './object_utils' ; import { MlPages } from '../../../common/constants/locator' ; type Accessor = '_a' | '_g' ; export type SetUrlState = ( accessor : Accessor , attribute : string | Dictionary < any > , value ? : any , replaceState ? : boolean ) => void ; export interface UrlState { searchString : string ; setUrlState : SetUrlState ; } /**
 * Set of URL query parameters that require the rison serialization.
 */ const risonSerializedParams = new Set ( [ '_a' , '_g' ] ) ; param queryParam function isRisonSerializationRequired ( queryParam ) { return risonSerializedParams . has ( queryParam ) ; } export function parseUrlState ( search ) { const urlState = { } ; const parsedQueryString = parse ( search , { sort : false } ) ; try { Object . keys ( parsedQueryString ) . forEach ( ( a ) => { if ( isRisonSerializationRequired ( a ) ) { urlState [ a ] = decode ( parsedQueryString [ a ] as string ) ; } else { urlState [ a ] = parsedQueryString [ a ] ; } } ) ; } catch ( error ) { console . error ( 'Could not read url state' , error ) ; } return urlState ; } export const urlStateStore = createContext < UrlState > ( { searchString : '' , setUrlState : ( ) => { } , } ) ; const { Provider } = urlStateStore ; export const UrlStateProvider = ( { children } ) => { const history = useHistory ( ) ; const { search : searchString } = useLocation ( ) ; const setUrlState = useCallback ( ( accessor , attribute , value ? , replaceState ? ) => { const prevSearchString = searchString ; const urlState = parseUrlState ( prevSearchString ) ; const parsedQueryString = parse ( prevSearchString , { sort : false } ) ; if ( ! Object . prototype . hasOwnProperty . call ( urlState , accessor ) ) { urlState [ accessor ] = { } ; } if ( typeof attribute === 'string' ) { if ( isEqual ( getNestedProperty ( urlState , `${ accessor }.${ attribute }` ) , value ) ) { return prevSearchString ; } urlState [ accessor ] [ attribute ] = value ; } else { const attributes = attribute ; Object . keys ( attributes ) . forEach ( ( a ) => { urlState [ accessor ] [ a ] = attributes [ a ] ; } ) ; } try { const oldLocationSearchString = stringify ( parsedQueryString , { sort : false , encode : false , } ) ; Object . keys ( urlState ) . forEach ( ( a ) => { if ( isRisonSerializationRequired ( a ) ) { parsedQueryString [ a ] = encode ( urlState [ a ] ) ; } else { parsedQueryString [ a ] = urlState [ a ] ; } } ) ; const newLocationSearchString = stringify ( parsedQueryString , { sort : false , encode : false , } ) ; if ( oldLocationSearchString !== newLocationSearchString ) { const newSearchString = stringify ( parsedQueryString , { sort : false } ) ; if ( replaceState ) { history . replace ( { search : newSearchString } ) ; } else { history . push ( { search : newSearchString } ) ; } } } catch ( error ) { console . error ( 'Could not save url state' , error ) ; } } , [ searchString ] ) ; return < Provider value = { { searchString , setUrlState } } > { children } < /Provider>; } ; export const useUrlState = ( accessor ) => { const { searchString , setUrlState : setUrlStateContext } = useContext ( urlStateStore ) ; const urlState = useMemo ( ( ) => { const fullUrlState = parseUrlState ( searchString ) ; if ( typeof fullUrlState === 'object' ) { return fullUrlState [ accessor ] ; } } , [ searchString ] ) ; const setUrlState = useCallback ( ( attribute , value ? , replaceState ? ) => { setUrlStateContext ( accessor , attribute , value , replaceState ) ; } , [ accessor , setUrlStateContext ] ) ; return [ urlState , setUrlState ] ; } ; type LegacyUrlKeys = 'mlExplorerSwimlane' ; export type AppStateKey = 'mlSelectSeverity' | 'mlSelectInterval' | 'mlAnomaliesTable' | MlPages | LegacyUrlKeys ; /**
 * Hook for managing the URL state of the page.
 */ export const usePageUrlState = < PageUrlState extends { } > ( pageKey , defaultState ? ) : [ PageUrlState , ( update : Partial < PageUrlState > , replaceState ? : boolean ) => void ] => { const [ appState , setAppState ] = useUrlState ( '_a' ) ; const pageState = appState ?. [ pageKey ] ; const resultPageState = useMemo ( ( ) => { return { ... ( defaultState ?? { } ) , ... ( pageState ?? { } ) , } ; } , [ pageState ] ) ; const onStateUpdate = useCallback ( ( update , replaceState ? ) => { setAppState ( pageKey , { ... resultPageState , ... update , } , replaceState ) ; } , [ pageKey , resultPageState , setAppState ] ) ; return useMemo ( ( ) => { return [ resultPageState , onStateUpdate ] ; } , [ resultPageState , onStateUpdate ] ) ; } ;