/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ import { DataIntegrityEnum, QueryExecution, QueryStatus } from '@ada/api'; import { ENV_STORYBOOK, ENV_TEST, getSolutionPersistenceKey } from '$config'; import { IAceEditor } from 'react-ace/lib/types'; import { QueryExecutionStatus } from '@ada/common'; import { apiHooks } from '$api'; import { useI18nContext } from '@ada/strings/dist/i18n/i18n-react'; import { useNotificationContext } from '$northstar-plus'; import { useQueryStatusPoller } from './hooks'; import React, { RefObject, createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; import useLocalStorage from 'react-use-localstorage'; const LOCAL_STORAGE_KEY_QUERYWORKBENCH_DRAWER_OPEN = getSolutionPersistenceKey('query.workbench.drawer.open', false); const LOCAL_STORAGE_KEY_QUERYWORKBENCH_QUERY = getSolutionPersistenceKey('query.workbench.query', true); const PERSIST = !ENV_TEST && !ENV_STORYBOOK; export enum LAYOUTS { DEFAULT = 'default', FULLSCREEN = 'fullscreen:results', } export interface QueryExecutionInfo extends QueryExecution { query: string; } export type QueryWorkbendLayout = LAYOUTS; export interface IQueryWorkbenchContext { isDrawerOpen: boolean; openDrawer: () => void; closeDrawer: () => void; toggleDrawer: () => void; layout: QueryWorkbendLayout; setLayout: React.Dispatch>; getQuery: () => string; setQuery: (query: string, fromEditor?: boolean) => void; queryStatus?: QueryStatus; isExecutingQuery: boolean; setIsExcutingQuery: (value: boolean) => void; executionInfo?: QueryExecutionInfo; setExecutionInfo: (executionInfo: QueryExecutionInfo | undefined) => void; dataIntegrity?: DataIntegrityEnum; setDataIntegrity: (dataIntegrity: DataIntegrityEnum | undefined) => void; editorRef: RefObject; startQuery: () => void; cancelQuery: () => void; } const QueryWorkbenchContext = createContext(undefined); export function useQueryWorkbench() { const context = useContext(QueryWorkbenchContext); if (context == null) throw new Error('Must wrap with QueryWorkbenchContext.Provider'); return context; } export const QueryWorkbenchProvider: React.FC<{}> = ({ children }) => { const { LL } = useI18nContext(); const { addError, addBrief } = useNotificationContext(); const [isPeristentDrawerOpen, setIsPersitentDrawerOpen] = useLocalStorage( LOCAL_STORAGE_KEY_QUERYWORKBENCH_DRAWER_OPEN, 'true', ); const [isDrawerOpen, setIsDrawerOpen] = useState(isPeristentDrawerOpen !== 'false'); const openDrawer = useCallback(() => { setIsDrawerOpen(true); }, [setIsDrawerOpen]); const closeDrawer = useCallback(() => { setIsDrawerOpen(false); }, [setIsDrawerOpen]); const toggleDrawer = useCallback(() => { setIsDrawerOpen((current) => !current); }, [setIsDrawerOpen]); useEffect(() => { setIsPersitentDrawerOpen(isDrawerOpen ? 'true' : 'false'); }, [isDrawerOpen]); // eslint-disable-line react-hooks/exhaustive-deps const [layout, setLayout] = useState(LAYOUTS.DEFAULT); const [isExecutingQuery, setIsExcutingQuery] = useState(false); const [executionInfo, setExecutionInfo] = useState(); const [dataIntegrity, setDataIntegrity] = useState(); const editorRef = useRef(null); const [persistentQuery, setPersistentQuery] = useLocalStorage( LOCAL_STORAGE_KEY_QUERYWORKBENCH_QUERY, 'SELECT * FROM ', ); const queryRef = useRef(persistentQuery); const getQuery = useCallback(() => queryRef.current, []); const setQuery = useCallback( (query: string, fromEditor?: boolean) => { console.debug('setQuery:', fromEditor, query); queryRef.current = query; // update the live ace editor if (fromEditor !== true && editorRef.current) { // setValue on document to enable undo https://github.com/ajaxorg/ace/issues/2558 editorRef.current.getSession().getDocument().setValue(query); } }, [editorRef], ); // persist query to local storage periodically and on unmount useEffect((): any => { if (PERSIST) { const timer = setInterval(() => { setPersistentQuery(getQuery()); }, 2500); return () => { clearInterval(timer); setPersistentQuery(getQuery()); }; } }, []); // eslint-disable-line react-hooks/exhaustive-deps const [postQuery] = apiHooks.usePostQuery({ retry: false, onError: useCallback( (error) => { setExecutionInfo(undefined); setIsExcutingQuery(false); addError({ header: LL.VIEW.QUERY.STATUS.FAILED.label(), content: error.message, }); }, [setIsExcutingQuery, addError], ), onSuccess: useCallback( ({ executionId }, { query: { query } }) => { setExecutionInfo({ executionId, query, // startTime, }); }, [setExecutionInfo], ), }); const startQuery = useCallback(async () => { // make sure we have very latest query const query = getQuery(); setExecutionInfo(undefined); setIsExcutingQuery(true); postQuery({ query: { query } }); }, [getQuery, setExecutionInfo, setIsExcutingQuery, postQuery]); const cancelQuery = useCallback(() => { setExecutionInfo(undefined); setIsExcutingQuery(false); }, [setExecutionInfo, setIsExcutingQuery]); const polledQueryStatus = useQueryStatusPoller(executionInfo?.executionId, { onFinally: useCallback(() => { // mark as not querying on final states (inclues any failed states and succeeded) setIsExcutingQuery(false); }, [setIsExcutingQuery]), onFailed: useCallback( ({ status, reason }) => { // reset execution into on failure setExecutionInfo(undefined); switch (status) { case 'ABORTED': case 'CANCELLED': { addBrief({ type: 'warning', header: LL.VIEW.QUERY.STATUS.CANCELLED.label(), content: reason, }); break; } case 'FAILED': { break; } case 'TIMED_OUT': { addError({ header: LL.VIEW.QUERY.STATUS.TIMED_OUT.label(), content: LL.VIEW.QUERY.STATUS.TIMED_OUT.hintText(), }); break; } } }, [addBrief, addError], ), }); const queryStatus = useMemo(() => { if (executionInfo?.executionId) { return ( polledQueryStatus || { status: QueryExecutionStatus.QUEUED, } ); } return undefined; }, [polledQueryStatus]); const context: IQueryWorkbenchContext = { isDrawerOpen, openDrawer, closeDrawer, toggleDrawer, getQuery, setQuery, queryStatus, isExecutingQuery, setIsExcutingQuery, executionInfo, setExecutionInfo, dataIntegrity, setDataIntegrity, editorRef, layout, setLayout, startQuery, cancelQuery, }; return {children}; };