\n {/* This is older code that was commented out. This was meant to allow a resize-window button to fit into these 10px at the bottom. */}\n {/*
*/}\n
\n { showExpandIcon &&
updateEditorHeight('maximize')} style={{ display: Number(editorHeight.split('px')[0]) >= editorMaxHeight() ? 'none' : 'block' }}>\n
\n \n \n \n
}\n { showExpandIcon &&
updateEditorHeight('minimize')} style={{ display: Number(editorHeight.split('px')[0]) >= Math.max(editorMaxHeight(), 101) ? 'block' : 'none' }}>\n
\n \n \n \n
}\n { markers && markers.length > 0 &&
\n {markers.length} match{markers.length > 1 ? 'es':''}\n }\n
\n
\n {/* \n * TODO: this is older code that allowed resizing the window. This comes with another line that's commented out above (that allows some grey-space for this draging icon).\n * Decide next whether to use it as is or change this logic to some extent.\n */}\n {/*
\n \n \n \n \n \n
*/}\n
\n );\n }\n);\n\nexport default Component;\n","import React from 'react';\n\ninterface TableauIconProps {\n size: string;\n}\n\nconst TableauIcon = ({ size }: TableauIconProps) => {\n return (\n
\n \n \n \n \n \n \n \n \n \n );\n};\n\nexport default TableauIcon;\n","import React from 'react';\n\ninterface ModeIconProps {\n size: string;\n}\n\nconst ModeIcon = ({ size }: ModeIconProps) => {\n return (\n
\n \n \n );\n};\n\nexport default ModeIcon;\n","import React from 'react';\n\ninterface KaggleIconProps {\n size: string;\n}\n\nconst KaggleIcon = ({ size }: KaggleIconProps) => {\n return (\n
\n \n \n );\n};\n\nexport default KaggleIcon;\n","import React from 'react';\n\ninterface AthenaIconProps {\n size: string;\n}\n\nconst AthenaIcon = ({ size }: AthenaIconProps) => {\n return (\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n );\n};\n\nexport default AthenaIcon;\n","import React from 'react';\n\ninterface GithubIconProps {\n size: string;\n}\n\nconst GithubIcon = ({ size }: GithubIconProps) => {\n return (\n
\n \n \n );\n};\n\nexport default GithubIcon;\n","import React from 'react';\n\ninterface NotionIconProps {\n size: string;\n}\n\nconst NotionIcon = ({ size }: NotionIconProps) => {\n return (\n
\n \n \n \n );\n};\n\nexport default NotionIcon;\n","import React from 'react';\n\ninterface SherloqIconProps {\n size: string;\n}\n\nconst SherloqIcon = ({ size }: SherloqIconProps) => {\n return (\n
\n \n \n \n \n \n \n \n \n );\n};\n\nexport default SherloqIcon;\n","import React from 'react';\n\ninterface BigQueryIconProps {\n size: string;\n}\n\nconst BigQueryIcon = ({ size }: BigQueryIconProps) => {\n return (\n
\n \n \n \n \n \n \n \n \n );\n};\n\nexport default BigQueryIcon;\n","import React from 'react';\n\ninterface SnowflakeIconProps {\n size: string;\n}\n\nconst SnowflakeIcon = ({ size }: SnowflakeIconProps) => {\n return (\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n );\n};\n\nexport default SnowflakeIcon;\n","import React from 'react';\n\ninterface DatabricksIconProps {\n size: string;\n}\n\nconst DatabricksIcon = ({ size }: DatabricksIconProps) => {\n return (\n
\n \n \n \n \n \n \n \n );\n};\n\nexport default DatabricksIcon;\n","import React from \"react\";\n\ninterface DataGripIconProps {\n size: string;\n}\n\nconst DataGripIcon = ({ size = '20px' }: DataGripIconProps) => {\n return (\n
\n\n\t\n\t\t \n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t \n\t\t \n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t \n\t\t \n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t \n\t\t \n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t \n\t\t \n\t \n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t \n\t\t\t\t\t\n\t\t\t\t \n\t\t\t \n\t\t\t\n\t\t \n\t \n \n \n\n );\n}\n\nexport default DataGripIcon;","import React from 'react';\n\ninterface PostgresIconProps {\n size: string;\n}\nconst Postgres = ({ size }: PostgresIconProps) => {\n return (\n
\n \n \n \n \n \n );\n};\n\nexport default Postgres;\n","import React from 'react';\n\ninterface PrestoIconProps {\n size: string;\n}\n\nconst PrestoIcon = ({ size }: PrestoIconProps) => {\n return (\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n );\n};\n\nexport default PrestoIcon;\n","import React from 'react';\nimport TableauIcon from './Tableau';\nimport ModeIcon from './Platforms/Mode';\nimport KaggleIcon from './Platforms/Kaggle';\nimport AthenaIcon from './Platforms/Athena';\nimport GithubIcon from './Platforms/Github';\nimport NotionIcon from './Platforms/Notion';\nimport SherloqIcon from './Platforms/Sherloq';\nimport BigQueryIcon from './Platforms/BigQuery';\nimport SnowflakeIcon from './Platforms/Snowflake';\nimport DatabricksIcon from './Platforms/Databricks';\nimport DataGripIcon from './Platforms/DataGrip';\nimport PostgresIcon from './Platforms/Postgres';\nimport PrestoIcon from './Platforms/Presto';\n\ninterface PlatformIconProps {\n origin: string;\n size: string;\n}\n\nexport const PlatformIcon = ({ origin, size }: PlatformIconProps) => {\n return (\n <>\n {origin === 'athena' ? (\n
\n ) : origin === 'bigquery' ? (\n
\n ) : origin === 'snowflake' ? (\n
\n ) : origin === 'modeAnalytics' ? (\n
\n ) : origin === 'github' ? (\n
\n ) : origin === 'tableau' ? (\n
\n ) : origin === 'notion' ? (\n
\n ) : origin === 'databricks' ? (\n
\n ) : origin === 'kaggle' ? (\n
\n ) : origin === 'datagrip' ? (\n
\n ) : origin === 'postgres' ? (\n
\n ) : origin === 'presto' ? (\n
\n ) : (\n
\n )}\n >\n );\n};\n","import React from 'react';\nimport { styled, Input as MUIInput, InputProps } from '@mui/material';\n\nexport const Input = styled((props: InputProps) => (\n
\n))(({ theme }) => {\n const { focused, border, background } = (theme.palette as any).input;\n return {\n padding: 0,\n '.MuiInput-input': {\n borderWidth: '1px',\n borderStyle: 'solid',\n borderColor: border,\n fontSize: '14px',\n backgroundColor: background,\n borderRadius: '5px',\n padding: '10px 12px',\n ':focus': {\n borderColor: focused\n }\n }\n };\n});\n","import React from 'react';\nimport {\n styled,\n Button as MUIButton,\n ButtonProps,\n IconButton,\n IconButtonProps\n} from '@mui/material';\n\ninterface NavRouteButton extends ButtonProps {\n isActive;\n}\n\nexport const Button = styled(MUIButton)
(\n ({ theme, isActive }) => {\n const { main } = theme.palette.primary;\n return {\n borderBottom: isActive ? `2px solid ${main}` : '1px solid transparent',\n borderRadius: '0px',\n padding: '2px 6px'\n };\n }\n);\n\nexport const Header = styled('header')(() => {\n return {\n padding: '0 24px'\n };\n});\n\nexport const RightAligned = styled('div')(() => {\n return {\n marginLeft: 'auti',\n display: 'flex',\n alignItems: 'center'\n };\n});\n\nexport const RepositoryStatesBtnsBox = styled('div')(() => {\n return {\n display: 'flex',\n alignItems: 'flex-end',\n marginRight: '12px',\n padding: '0px',\n height: '34px',\n borderRadius: '4px',\n columnGap: '4px'\n };\n});\n\nexport const RepoDisplayToggleIconBtn = styled(\n (props: IconButtonProps & { selected: boolean }) => \n)(({ selected, theme }) => {\n const {\n primary: { main, light },\n background,\n grey\n } = theme.palette;\n const backgroundColor = selected ? main : light;\n return {\n padding: '4px',\n borderRadius: '4px',\n backgroundColor,\n color: selected ? background.default : '#000',\n ':hover': {\n color: grey['300'],\n backgroundColor\n }\n };\n});\n\nexport const NewFolderBtn = styled(MUIButton)(() => {\n return {\n display: 'flex',\n color: '#fff',\n textTransform: 'none',\n fontSize: '12px',\n fontWeight: 400,\n alignItems: 'center',\n columnGap: '4px'\n };\n});\n","import { useMemo } from 'react';\nimport { Stack } from '@mui/material';\nimport FolderIcon from '@mui/icons-material/Folder';\nimport ApartmentIcon from '@mui/icons-material/Apartment';\nimport HistoryIcon from '@mui/icons-material/History';\nimport { sx } from '../../styles/MUI/styles';\nimport { useLocation } from 'react-router-dom';\nimport { Button } from './component';\nimport TableChartIcon from '@mui/icons-material/TableChart';\nimport { ConditionComponent } from '../../lib/utils';\nimport { useSimplyRoutingIntegration } from '../../hooks/platform-integration/simpliIntegration';\nimport { useUserInfoSelector } from '../../store/auth/selectors';\nimport { useDarkModeState } from '../../store/themeMode/selectors';\n\ntype ClickEventHandler = onClickHandler;\n\ninterface ActivePagesPayload {\n histoyPageActive: boolean;\n privatePageActive: boolean;\n companyPageActive: boolean;\n allPageActive?: boolean;\n}\n\ninterface DefaultSubPagesProps {\n variant: 'search' | 'default';\n onClickMy?: ClickEventHandler;\n onClickCompany?: ClickEventHandler;\n onClickHistory?: ClickEventHandler;\n onClickAll?: ClickEventHandler;\n activePages?: ActivePagesPayload;\n onClickTables?: ClickEventHandler;\n}\n\nconst PageHeaderIconStyles = {\n fontSize: '16px',\n marginRight: '4px'\n};\n\nexport const SubPages = ({\n variant,\n onClickCompany,\n onClickHistory,\n onClickMy,\n onClickAll,\n onClickTables\n}: DefaultSubPagesProps) => {\n const location = useLocation();\n const { authorize: authorizeOnSimply } = useSimplyRoutingIntegration();\n const userInfo = useUserInfoSelector();\n const darkMode = useDarkModeState();\n const companyName = userInfo && userInfo.email ? userInfo.email.split('@')[1].split('.')[0] : ''\n const companyStr = companyName ? companyName.charAt(0).toUpperCase() + companyName.slice(1) : ''\n const {\n histoyPageActive,\n privatePageActive,\n companyPageActive,\n allPageActive,\n tablesPageActive\n } = useMemo(() => {\n const activePages = {\n histoyPageActive: false,\n privatePageActive: false,\n companyPageActive: false,\n allPageActive: false,\n tablesPageActive: false\n };\n if (location.pathname.includes('saved')) {\n activePages.privatePageActive = true;\n } else if (location.pathname.includes('public')) {\n activePages.companyPageActive = true;\n } else if (location.pathname.includes('history')) {\n activePages.histoyPageActive = true;\n } else if (location.pathname.includes('all')) {\n activePages.allPageActive = true;\n } else if (location.pathname.includes('tables')) {\n activePages.tablesPageActive = true;\n }\n return activePages;\n }, [location.pathname]);\n\n const AllNode = useMemo(() => {\n if (variant === 'search') {\n return (\n \n All\n \n );\n }\n return null;\n }, [variant]);\n\n return (\n \n {AllNode}\n \n \n My\n \n \n \n {companyStr}\n \n \n \n History\n \n \n \n \n Tables\n \n \n \n );\n};\n","import { default as MUIPopover, PopoverProps } from '@mui/material/Popover';\nimport React from 'react';\n\nexport const Popover: React.FC = ({ style, ...props }) => {\n return (\n \n );\n};\n","import createSvgIcon from './utils/createSvgIcon';\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon( /*#__PURE__*/_jsx(\"path\", {\n d: \"M20 5H4c-1.1 0-1.99.9-1.99 2L2 17c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm-9 3h2v2h-2V8zm0 3h2v2h-2v-2zM8 8h2v2H8V8zm0 3h2v2H8v-2zm-1 2H5v-2h2v2zm0-3H5V8h2v2zm9 7H8v-2h8v2zm0-4h-2v-2h2v2zm0-3h-2V8h2v2zm3 3h-2v-2h2v2zm0-3h-2V8h2v2z\"\n}), 'Keyboard');","import createSvgIcon from './utils/createSvgIcon';\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon( /*#__PURE__*/_jsx(\"path\", {\n d: \"M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"\n}), 'MoreVertRounded');","import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport match from 'autosuggest-highlight/match';\nimport parse from 'autosuggest-highlight/parse';\nimport { useDarkModeState } from '../../store/themeMode/selectors';\nimport { useSubscribeToCopyEvents } from '../../lib/hooks';\nimport { addStemsToText } from '../../utils/helpers';\n\nexport interface DisplayQueryEditorProps {\n query?: string;\n onManualCopy?: (text: string) => Promise | void;\n searchText?: string;\n}\n\nexport const DisplayQueryEditor = ({\n query,\n onManualCopy,\n searchText = ''\n}: DisplayQueryEditorProps) => {\n const darkMode = useDarkModeState();\n const [isResizing, setIsResizing] = useState(false)\n\n const getKeywordMatches = useCallback(\n (text: string) => {\n if (typeof text !== 'string') {\n return [];\n } // Handle non-string inputs\n const matches = match(text, addStemsToText(searchText), {\n insideWords: true\n });\n return parse(text, matches ?? []);\n },\n [searchText]\n );\n\n const highlightMap = useMemo(() => {\n const highlightMap = getKeywordMatches(query);\n return highlightMap\n }, [query]);\n\n const highlightedQuery = query.split(new RegExp(`(${searchText})`, 'gi')).map((part, index) =>\n part.toLowerCase() === searchText.toLowerCase() ?\n {part} :\n part\n );\n\n const handleMouseDown = (e: React.MouseEvent) => {\n const startY = e.clientY;\n const startHeight = preRef.current?.offsetHeight || 0;\n\n const handleMouseMove = (moveEvent: MouseEvent) => {\n setIsResizing(true)\n const newHeight = startHeight + (moveEvent.clientY - startY);\n if (preRef.current) {\n preRef.current.style.height = `${newHeight}px`;\n }\n };\n\n const handleMouseUp = (e) => {\n setTimeout(() => {\n setIsResizing(false)\n }, 100)\n document.removeEventListener('mousemove', handleMouseMove);\n document.removeEventListener('mouseup', handleMouseUp);\n };\n\n document.addEventListener('mousemove', handleMouseMove);\n document.addEventListener('mouseup', handleMouseUp);\n };\n\n\n const subscribeRef = useSubscribeToCopyEvents({\n onCopy: onManualCopy,\n enable: Boolean(onManualCopy)\n }).ref;\n const preRef = useRef(null);\n\n const combinedRef = useCallback(\n (node: HTMLPreElement) => {\n subscribeRef.current = node;\n preRef.current = node;\n },\n [subscribeRef]\n );\n\n useEffect(() => {\n if (preRef.current) {\n const firstMark = preRef.current.querySelector('mark');\n if (firstMark) {\n preRef.current.scrollTop = firstMark.offsetTop - preRef.current.offsetTop;\n }\n }\n }, [searchText, query]);\n\n const spanRefs = useRef([]);\n\n useEffect(() => {\n // Find the first highlighted part\n const firstHighlightedIndex = highlightMap.findIndex(part => part.highlight);\n\n if (firstHighlightedIndex !== -1 && spanRefs.current[firstHighlightedIndex]) {\n // Calculate the offset of the highlighted span relative to the pre element\n const highlightedSpan = spanRefs.current[firstHighlightedIndex];\n const preElement = preRef.current;\n \n // Scroll the pre element to the position of the highlighted span\n preElement.scrollTop = highlightedSpan.offsetTop - preElement.offsetTop;\n }\n }, [highlightMap]);\n\n return (\n {\n if (isResizing) {\n e.preventDefault();\n e.stopPropagation();\n }\n }}>\n
element\n // overflow: 'auto', // add overflow auto to enable scrolling\n // maxHeight: '400px', // you can adjust this based on your requirements\n // height: '200px', // initial height\n // resize: 'vertical', // allows vertical resizing by default\n // padding: '10px',\n // boxSizing: 'border-box'\n }}\n >\n {highlightMap.map((part, idx) => (\n spanRefs.current[idx] = el}\n >\n {part.text}\n \n ))}\n \n
\n \n \n \n \n \n
\n
\n );\n};\n","import { useMemo } from 'react';\nimport { useFolderTree } from '../../../lib/hooks/useFolderTree';\nimport { selectFolderFromFoldersByUUID } from '../../../utils/helpers';\nimport { useUserInfoSelector } from '../../../store/auth/selectors';\nimport { QueryType } from '../../../adapters/query/interface';\n\ninterface Params {\n parentFolderId: string;\n type: string;\n query_owner: string;\n}\n\nexport const useQueryItemType = ({\n parentFolderId,\n type,\n query_owner\n}: Params): QueryType => {\n const folders = useFolderTree();\n const user = useUserInfoSelector();\n const isHistory = type.includes('history');\n\n const queryFolderType = useMemo(() => {\n const queryParent = selectFolderFromFoldersByUUID(parentFolderId, folders);\n if (queryParent) {\n return queryParent.shared_type;\n }\n }, [parentFolderId, folders]);\n\n const queryType = useMemo(() => {\n if (isHistory) return 'history';\n const username = `${user?.first_name} ${user?.last_name}`;\n const queryCreatedByUser = username === query_owner;\n if (queryCreatedByUser) return 'personal';\n if (queryFolderType === 'shared') return 'shared';\n return 'public';\n }, [user, isHistory, queryFolderType]);\n\n return queryType;\n};\n","import React, { useCallback, useMemo } from 'react';\nimport match from 'autosuggest-highlight/match';\nimport parse from 'autosuggest-highlight/parse';\nimport { Typography, Stack, Box } from '@mui/material';\nimport { PlatformIcon } from '../../Icons/PlatformIcon';\nimport { footerQueryTypeIconContainerStyles } from '../styles';\nimport { UTCtoLocal, localTime } from '../../../pages/Editor/helpers/helpers';\nimport { QueryItemFooterIcon } from './QueryItemFooterIcon';\nimport { useQueryItemType } from '../hooks/useQueryItemIconType';\nimport { addStemsToText, getTimePassedSinceString } from '../../../utils/helpers';\nimport { CustomTooltip } from '../../../atoms/CustomTooltip';\n\ninterface IQueryItemFooterData {\n type: string;\n query_origin: string;\n query_owner_username: string;\n query_created_at: string;\n parentFolderId: string;\n showHighlight: boolean;\n keyword: string;\n edited: any;\n}\n\ninterface QueryItemFooterProps {\n data: IQueryItemFooterData;\n}\n\nexport const QueryItemFooter = ({ data }: QueryItemFooterProps) => {\n const transformedDate = localTime(data?.query_created_at);\n const username = data?.query_owner_username;\n const queryType = useQueryItemType({\n parentFolderId: data.parentFolderId,\n query_owner: data.query_owner_username,\n type: data.type\n });\n const isHistory = queryType === 'history';\n const getKeywordMatches = useCallback(\n (text: string) => {\n if (typeof text !== 'string') {\n return [];\n } // Handle non-string inputs\n const matches = match(text, addStemsToText(data.keyword), {\n insideWords: true\n });\n return parse(text, matches ?? []);\n },\n [data.keyword]\n );\n const highlightMap = useMemo(() => {\n const highlightMap = getKeywordMatches(username);\n return highlightMap;\n }, [data.keyword, username]);\n\n return (\n \n
\n \n \n {isHistory ? 'Query was run by' : 'Created by'}\n \n \n {highlightMap.map((part, idx) => (\n \n {part.text}\n \n ))}\n \n {/* \n \n */}\n \n
\n {data?.edited?.created_at &&\n \n \n Edited {getTimePassedSinceString(new Date(data?.edited?.created_at))}\n \n \n }\n \n
\n );\n};\n","import { styled, Box } from '@mui/material';\n\nexport const Container = styled(Box)(({ theme }) => {\n const {\n background: { paper },\n primary: { main }\n } = theme.palette;\n const border = (theme.palette as any).input.border;\n return {\n position: 'relative',\n paddingBottom: '8px',\n margin: '8px 0px',\n boxSizing: 'border-box',\n backgroundColor: paper,\n border: `1px solid ${border}`,\n borderRadius: '3px',\n borderLeft: `4px solid ${main}`,\n padding: '8px',\n transition: 'all 0.7s ease-in-out 0.1s',\n cursor: 'pointer'\n };\n});\n","import React, { useMemo } from 'react';\nimport { Typography } from '@mui/material';\n\nimport { shortenString } from '../../../pages/Editor/helpers/helpers';\nimport { CustomTooltip } from '../../../atoms/CustomTooltip';\ninterface Props {\n value: string;\n onClick: (e: any) => void;\n}\nexport const QueryName = ({ value, onClick }: Props) => {\n const { displayValue, showToolTip } = useMemo(() => {\n return {\n showToolTip: value ? value.length > 30 : false,\n displayValue: shortenString(`${value}`, 120)\n };\n }, [value]);\n\n const node = (\n \n {displayValue}\n \n );\n\n if (showToolTip) {\n return (\n \n {node}\n \n );\n }\n\n return node;\n};\n","import React, { useMemo } from 'react';\nimport { Typography } from '@mui/material';\n\nimport { shortenString } from '../../../pages/Editor/helpers/helpers';\nimport { CustomTooltip } from '../../../atoms/CustomTooltip';\nimport { useDarkModeState } from '../../../store/themeMode/selectors';\ninterface Props {\n value: string;\n onClick: (e: any) => void;\n}\nexport const QueryDescription = ({ value, onClick }: Props) => {\n const darkMode = useDarkModeState()\n const { displayValue, showToolTip } = useMemo(() => {\n return {\n showToolTip: value ? value.length > 120 : false,\n displayValue: shortenString(`${value}`, 120)\n };\n }, [value]);\n\n const node = (\n \n {displayValue}\n \n );\n\n if (showToolTip) {\n return (\n \n {node}\n \n );\n }\n\n return node;\n};\n","import Tags from '../Tags/Tags';\nimport unionBy from 'lodash/unionBy';\nimport { toast } from 'react-toastify';\nimport React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { useAppDispatch } from '../../store';\nimport { sx } from '../../styles/MUI/styles';\nimport { useNavigate } from 'react-router-dom';\nimport DeleteModal from '../modals/DeleteModal';\nimport CustomSqlEditor, { AceEditorExtraProps } from '../CustomSqlEditor';\nimport CopyToClipboard from 'react-copy-to-clipboard';\nimport { Popover } from '../../lib/components/Popover';\nimport { CustomTooltip } from '../../atoms/CustomTooltip';\nimport MixedPanelEvent from '../../utils/analytics/mixPanel';\nimport { useDeleteQuery, useMoveQuery } from '../../lib/hooks';\nimport { useUserInfoSelector } from '../../store/auth/selectors';\nimport { sharedFolderThunk } from '../../store/sharedFolders/thunk';\nimport { folderSummaryThunk } from '../../store/folderSummary/thunk';\nimport { Typography, Stack, Menu, MenuItem, Box, Checkbox } from '@mui/material';\nimport { shortenString } from '../../pages/Editor/helpers/helpers';\nimport {\n ContentCopyRounded,\n Link,\n MoreVertRounded,\n Keyboard,\n SubdirectoryArrowRightRounded,\n} from '@mui/icons-material';\nimport { useFolderSummarySelector } from '../../store/folderSummary/selectors';\nimport { useSharedFoldersSelector } from '../../store/sharedFolders/selectors';\nimport { Button } from '@mui/material';\nimport { useDarkModeState } from '../../store/themeMode/selectors';\nimport AutoAwesomeIcon from '@mui/icons-material/AutoAwesome';\nimport { BrowserUtils, RenderCondition, RenderIf } from '../../lib/utils';\nimport { parseDescription } from '../../lib/utils/helperFns/transformFns';\nimport { DisplayQueryEditor } from '../CustomSqlEditor/displayQueryEditor';\nimport { QueryItemFooter } from './components/footer';\nimport { MoveQueryModal } from '../modals/moveQueryModal';\nimport { Container } from './components/container';\nimport { QueryDetailInput } from '../../pages/Editor/components/input';\nimport { queriesAsyncThunk } from '../../store/queries/queries.thunk';\nimport { QueryName } from './components/queryName';\nimport { QueryDescription } from './components/queryDescription';\nimport { addShortcut, deleteShortcut, updateQueryVersion } from '../../pages/Editor/api/fetch';\nimport match from 'autosuggest-highlight/match';\nimport parse from 'autosuggest-highlight/parse';\nimport { formatSql } from '../../utils/formaters';\nimport AceEditor from 'react-ace';\nimport { Logger } from '../../utils/browser/logger';\nimport { addStemsToText } from '../../utils/helpers';\nimport ApiClient from '../../lib/utils/apiClient';\nimport MemberActions from '../../pages/Editor/components/MemberActions';\n\ninterface SearchHighlightMap {\n title?: any;\n description?: any;\n parentFolder?: any;\n query?: any;\n}\n\ninterface Props {\n query: string;\n query_uid: string;\n query_name: string;\n query_origin: string;\n query_created_at: string;\n query_description: string;\n query_owner_username: string;\n queryTags?: any[];\n tags?: any[];\n type: string;\n folderName?: string;\n folderId?: string;\n pageType?: 'search' | 'query';\n user_uid?: string;\n searchResultsProps?: {\n showHighlight?: boolean;\n keyword?: string;\n };\n}\n\ninterface QueryMetaSummary {\n query_name: string;\n query_description: string;\n query_string: string;\n}\n\nconst autoDescriptionStyle = {\n backgroundColor: '#2E7D3233',\n display: 'flex',\n borderRadius: '4px',\n minWidth: '16px'\n};\n\nfunction selectText(elementId: string) {\n const element = document.getElementById(elementId) as HTMLInputElement;\n \n if (element) {\n element.select()\n element.scrollLeft = 0;\n }\n}\n\nconst QueryItem: React.FC<\n Props & { className?: string; disableOnClick?: boolean }\n> = ({\n query,\n query_uid,\n query_name,\n query_origin,\n query_created_at,\n query_description,\n query_owner_username,\n queryTags = [],\n tags,\n type,\n folderName,\n folderId,\n pageType,\n user_uid,\n searchResultsProps,\n disableOnClick\n}) => {\n const navigate = useNavigate();\n const dispatch = useAppDispatch();\n const userInfo = useUserInfoSelector();\n const [anchorEl, setAnchorEl] = React.useState(null);\n const [showViewIcon, setShowViewIcon] = React.useState(false);\n const darkMode = useDarkModeState();\n const aceEditorRef = useRef(null);\n const queryItemRef = useRef(null);\n const [extraEditorProps, setExtraEditorProps] = useState(\n {}\n );\n const metaRef = useRef({\n query_name,\n query_description,\n query_string: query\n }); // using a ref here to hold a reference to the meta summary across updates since the redux state for updating query and fetching folder content are in diff slices.\n const [meta, setMeta] = useState({\n query_name,\n query_description: query_description ?? '',\n query_string: query\n });\n\n // const allowMetaEdit = userInfo ? user_uid === userInfo.uid : false;\n const allowMetaEdit = true\n const [isEditingQuery, setIsEditingQuery] = useState(false);\n const [isEditingName, setIsEditingName] = useState(false);\n const [isEditingDescription, setIsEditingDescription] = useState(false);\n const showHighlight = Boolean(\n pageType === 'search' &&\n searchResultsProps?.showHighlight &&\n searchResultsProps.keyword\n );\n\n const onQueryNameClick = (e) => {\n if (allowMetaEdit) {\n e.stopPropagation();\n e.preventDefault();\n setIsEditingName(true)\n setTimeout(() => {\n if (document.getElementById('query-name')) {\n document.getElementById('query-name').focus()\n }\n }, 100)\n setTimeout(() => {\n if (document.getElementById('query-name')) {\n selectText('query-name')\n }\n }, 150)\n\n }\n }\n\n const onQueryDescriptionClick = (e) => {\n if (allowMetaEdit) {\n e.stopPropagation();\n e.preventDefault();\n setIsEditingDescription(true)\n setTimeout(() => {\n if (document.getElementById('query-description')) {\n document.getElementById('query-description').focus()\n }\n }, 100)\n setTimeout(() => {\n if (document.getElementById('query-description')) {\n selectText('query-description')\n }\n }, 150)\n }\n }\n\n const handleOnChangeMeta = (e: React.ChangeEvent) => {\n const name = e.target.name;\n if (name === 'query-name') {\n setMeta(p => ({ ...p, query_name: e.target.value }));\n } else if (name === 'query-description') {\n setMeta(p => ({ ...p, query_description: e.target.value }));\n }\n };\n\n const updateQueryMeta = (payload: Omit) => {\n if (meta.query_description !== metaRef.current.query_description) {\n ApiClient.post('queries/query_seen', { query_uid, action_type: 3 }, {\n isProtected: true,\n })\n }\n if (meta.query_name !== metaRef.current.query_name) {\n ApiClient.post('queries/query_seen', { query_uid, action_type: 2 }, {\n isProtected: true,\n })\n }\n\n dispatch(\n queriesAsyncThunk.update({\n uid: query_uid!,\n payload: {\n name: payload.query_name!,\n description: payload.query_description!,\n query: query\n },\n source: 'list'\n })\n ).then(() => {\n toast('Updated successfully', { type: 'success' });\n metaRef.current = { ...payload, query_string: query };\n });\n };\n\n const handleOnMetaKeyDown = (e: React.KeyboardEvent) => {\n const isEnter = e.key === 'Enter';\n const noShiftKey = !e.shiftKey;\n if (isEnter && noShiftKey) {\n (e.target as HTMLInputElement).blur();\n }\n if (e.key === 'Escape') {\n handleOnMetaBlur(null, isEditingName ? 'query-name' : 'query-description')\n }\n };\n\n const blurTimeoutRef = useRef(null);\n\n const handleQueryBlur = (value: string) => {\n handleUpdateQuery();\n \n if (blurTimeoutRef.current) {\n clearTimeout(blurTimeoutRef.current);\n }\n \n blurTimeoutRef.current = setTimeout(() => {\n setIsEditingQuery(false);\n blurTimeoutRef.current = null;\n }, 200);\n }\n \n const handleQueryFocus = (value: string) => {\n if (blurTimeoutRef.current) {\n clearTimeout(blurTimeoutRef.current);\n blurTimeoutRef.current = null;\n }\n \n setIsEditingQuery(true);\n }\n \n useEffect(() => {\n return () => {\n if (blurTimeoutRef.current) {\n clearTimeout(blurTimeoutRef.current);\n }\n };\n }, []);\n\n const handleOnMetaBlur = (e?: React.FocusEvent, fieldName?: string) => {\n const field = e ? e.target.name : fieldName;\n if (field === 'query-name') {\n const nameUpdated = meta.query_name !== metaRef.current.query_name;\n if (nameUpdated) {\n updateQueryMeta({\n query_description: metaRef.current.query_description,\n query_name: meta.query_name\n });\n }\n setTimeout(() => {\n setIsEditingName(false);\n }, 200)\n } else if (field === 'query-description') {\n const descriptionUpdated =\n meta.query_description !== metaRef.current.query_description;\n if (descriptionUpdated) {\n updateQueryMeta({\n query_description: meta.query_description,\n query_name: metaRef.current.query_name\n });\n }\n setTimeout(() => {\n setIsEditingDescription(false);\n }, 200)\n }\n };\n\n const updateQueryString = async () => {\n const queryVersion = {\n query_uid: query_uid,\n name: meta.query_name,\n description: meta.query_description,\n query: meta.query_string,\n user_uid: user_uid,\n created_at: new Date(query_created_at).getTime() / 1000,\n updated_at: new Date().getTime() / 1000\n };\n const {\n data: { id }\n } = await updateQueryVersion(queryVersion);\n ApiClient.post('queries/query_seen', { query_uid, action_type: 4 }, {\n isProtected: true,\n })\n MixedPanelEvent.queryInListAction({\n actionName: 'updated',\n query: {\n description: meta.query_description,\n name: meta.query_name,\n query: meta.query_string,\n uid: query_uid,\n created_at: query_created_at,\n user: userInfo,\n id\n }\n });\n toast('Query updated successfully', {\n type: 'success',\n hideProgressBar: true\n });\n metaRef.current.query_string = meta.query_string;\n };\n\n const handleOnChangeQueryString = (newString: string) => {\n setMeta(p => ({ ...p, query_string: newString }));\n };\n\n const handleUpdateQuery = () => {\n const queryStringUpdated =\n meta.query_string !== metaRef.current.query_string;\n if (queryStringUpdated) {\n updateQueryString();\n }\n }\n\n const handleOnEditorKeyDown = (ev: React.KeyboardEvent) => {\n const isEscape = ev.key === 'Escape';\n // const notShift = !ev.shiftKey;\n if (isEscape) {\n ev.stopPropagation();\n aceEditorRef.current?.blur();\n }\n };\n\n const scrollIntoView = () => {\n // Find the first element with the class 'scroll-container'\n const scrollContainer = document.querySelector('.scroll-container');\n \n if (scrollContainer && queryItemRef.current) {\n let offsetTop = -180;\n let element = queryItemRef.current;\n \n // Traverse up the DOM tree until we reach the scroll container\n while (element && element !== scrollContainer) {\n offsetTop += element.offsetTop;\n element = element.offsetParent;\n }\n \n // Scroll the container to bring the query item into view\n scrollContainer.scrollTo({ top: offsetTop, behavior: 'smooth' });\n }\n };\n\n const sx1 = {\n fontSize: 16,\n color: darkMode ? 'white' : '#252631',\n '&:hover': { color: 'primary.main' }\n };\n const folderSummary = useFolderSummarySelector();\n const folderSummaryShared = useSharedFoldersSelector();\n const folders = unionBy(folderSummary.list, folderSummaryShared.list, 'uid');\n const [isShortcut, setIsShortcut] = useState(false)\n\n const [anchorEl2, setAnchorEl2] = useState(null);\n const reloadSavedQueries = async () => {\n setTimeout(() => {\n navigate('/saved-queries');\n }, 500);\n };\n let {\n moveQuery,\n openModal: openMoveQueryModal,\n toggleModal: toggleMoveQueryModal,\n onSelectFolder: onSelectMoveQueryFolder,\n selectedFolder: selectedMoveQueryFolder\n } = useMoveQuery({ onCompleteMove: reloadSavedQueries });\n const {\n deleteQuery,\n openModal: openDeleteQueryModal,\n toggleModal: toggleDeleteQueryModal\n } = useDeleteQuery();\n\n const handleOnManuallyCopyEditorText = (text: string) => {\n const currentUrl = location.origin + location.pathname;\n ApiClient.post('queries/query_seen', { query_uid, action_type: 9 }, {\n isProtected: true,\n })\n MixedPanelEvent.queryManuallyCopied({\n currentUrl,\n queryId: query_uid,\n copiedText: text\n });\n };\n\n const { isAuto } = parseDescription(query_description);\n\n const handleDeletion = () => {\n deleteQuery(query_uid, folderId!);\n toggleDeleteQueryModal();\n };\n const navigateToEditorPage = () => {\n if (disableOnClick || isEditingName || isEditingDescription || isEditingQuery) {\n return;\n }\n ApiClient.post('queries/query_seen', { query_uid, action_type: 6 }, {\n isProtected: true,\n })\n if (\n type === 'saved' ||\n type === 'shared' ||\n (pageType !== 'search' && !type.includes('history'))\n ) {\n MixedPanelEvent.queryClicked(query);\n const encodedFolderName = encodeURIComponent(folderName);\n navigate(\n `/queries/${encodedFolderName}/${query_uid}/${type}/${folderId}`\n );\n }\n if (pageType === 'search' && !type.includes('history')) {\n navigate(\n `/queries/search/${folderName}/${query_uid}/${type}/${folderId}`\n );\n }\n if (type === 'my-history') {\n MixedPanelEvent.queryHistoryClicked(query);\n navigate(`/queries/hitory/my/${query_uid}`);\n }\n if (type === 'history') {\n MixedPanelEvent.queryHistoryClicked(query);\n navigate(`/queries/hitory/public/${query_uid}`);\n }\n };\n\n const handleShareQuery = e => {\n e.stopPropagation();\n MixedPanelEvent.editorListItemLinkAction(query, query_uid);\n ApiClient.post('queries/query_seen', { query_uid, action_type: 10 }, {\n isProtected: true,\n })\n const url = `${process.env.REACT_APP_SHERLOQ_AUTH_URL}/queryInfo/?queryId=${query_uid}`;\n BrowserUtils.copyTextToClipboard(url);\n toast('Shared link successfully copied to clipboard', {\n type: 'success'\n });\n };\n\n const getKeywordMatches = useCallback(\n (text: string) => {\n if (typeof text !== 'string') {\n return [];\n } // Handle non-string inputs\n const matches = match(text, addStemsToText(searchResultsProps?.keyword), {\n insideWords: true\n });\n return parse(text, matches ?? []);\n },\n [searchResultsProps?.keyword]\n );\n\n const formattedQuery = useMemo(() => {\n // return formatSql(meta.query_string);\n return meta.query_string;\n }, [meta.query_string]);\n\n const editorMarkers = useMemo(() => {\n const queryArray = formattedQuery?.toLowerCase().split('\\n') ?? [];\n const markers = [];\n queryArray.forEach((el, i, arr) => {\n addStemsToText(searchResultsProps?.keyword.toLowerCase()).split(' ').forEach(keyword => {\n if (keyword && el.includes(keyword)) {\n markers.push({\n startRow: i,\n startCol: queryArray[i].indexOf(keyword),\n endRow: i,\n endCol: arr[i].indexOf(keyword) + keyword.length,\n className: 'ace_highlighter',\n type: 'text'\n });\n }\n })\n \n });\n\n return markers;\n }, [searchResultsProps?.keyword, formattedQuery]);\n\n const [editorEl, setEditorEl] = useState(null)\n const [firstMarkerRow, setFirstMarkerRow] = useState(0)\n\n const editorMaxHeight = () => {\n const editor = editorEl\n if (!editor) return\n // const editor = aceEditorRef.current.editor;\n const lineHeight = editor.renderer.lineHeight;\n const contentHeight = editor.getSession().getScreenLength() * lineHeight;\n \n // Adding a buffer to ensure all lines are visible\n const buffer = lineHeight * 2; // Adding one line height as buffer\n return contentHeight + buffer\n }\n\n const handleOnLoadEditor = (editor: AceEditor['editor']) => {\n if (editorEl) return\n setEditorEl(editor)\n const firstMarkerIsOutOfView = editorMarkers[0]?.startRow > 6;\n if (firstMarkerIsOutOfView) {\n setFirstMarkerRow(editorMarkers[0]?.startRow);\n editor.scrollToRow(editorMarkers[0]?.startRow);\n }\n };\n\n const preventScrolling = (e) => {\n if (editorEl && !editorEl.isFocused()) {\n editorEl.scrollToRow(firstMarkerRow)\n if (document.getElementsByClassName('scroll-container').length) {\n document.getElementsByClassName('scroll-container')[0].scrollBy(0, e.deltaY);\n }\n if (document.getElementsByClassName('search-list-container').length) {\n document.getElementsByClassName('search-list-container')[0].scrollBy(0, e.deltaY);\n }\n }\n if (editorEl && editorEl.isFocused() && editorEl.container.clientHeight + 16 < editorMaxHeight()) {\n e.preventDefault();\n e.stopPropagation();\n }\n };\n\n useEffect(() => {\n if (editorEl) {\n editorEl.container.addEventListener('wheel', preventScrolling)\n }\n return (() => {\n if (editorEl) {\n editorEl.container.removeEventListener('wheel', preventScrolling)\n }\n })\n }, [editorEl])\n\n const handleShortcutCheckboxChange = (e) => {\n e.stopPropagation()\n e.preventDefault()\n if (isShortcut) {\n if (query_uid) {\n deleteShortcut(query_uid);\n toast('Snippet has been removed successfully.', {\n type: 'success'\n });\n MixedPanelEvent.deleteQueryFromSnippets(\n query ?? 'Query string n/a'\n );\n }\n } else {\n if (query_uid) {\n addShortcut(query_uid);\n toast('Snippet added successfully. Type @@ in your query editor to open Snippets menu.', {\n type: 'success'\n });\n MixedPanelEvent.setQueryAsSnippet(query ?? 'Query string n/a');\n }\n }\n ApiClient.post('queries/query_seen', { query_uid, action_type: 7 }, {\n isProtected: true,\n })\n setIsShortcut(!isShortcut);\n };\n\n const highlightMap = useMemo(() => {\n const highlightMap: SearchHighlightMap = {\n description: [],\n parentFolder: [],\n title: [],\n query: []\n };\n if (\n pageType === 'search' &&\n searchResultsProps?.showHighlight &&\n searchResultsProps?.keyword\n ) {\n const parentFolderName = shortenString(`${folderName}`, 30);\n // create highlight map\n const description = meta?.query_description.length > 50 ? meta?.query_description.substring(0, 50) + '...' : meta?.query_description\n highlightMap.title = getKeywordMatches(meta?.query_name);\n highlightMap.description = getKeywordMatches(description);\n highlightMap.parentFolder = getKeywordMatches(parentFolderName);\n }\n return highlightMap;\n }, [pageType, searchResultsProps, meta, folderName]);\n\n useEffect(() => {\n if (folderSummary && folderSummary.shortcuts && folderSummary.shortcuts.length) {\n setIsShortcut(folderSummary.shortcuts.find(sc => sc.query.uid === query_uid))\n }\n }, [folderSummary.shortcuts])\n\n const [querySeen, setQuerySeen] = useState([]);\n const [querySeenUsers, setQuerySeenUsers] = useState([]);\n\n useEffect(() => {\n if ((type.includes('history'))) return;\n ApiClient.get(`/queries/${query_uid}/query_seen`, { isProtected: true }).then((res => {\n const allQueryseen = [...res.data, {\n created_at: query_created_at,\n action_type: 1,\n user: {\n first_name: query_owner_username.split(' ')[0],\n last_name: query_owner_username.split(' ')[1],\n }\n }]\n setQuerySeen(allQueryseen.sort((a, b) => (new Date(b.created_at)).getTime() - (new Date(a.created_at)).getTime()))\n const tempSeenUsers = []\n \n allQueryseen.sort((a, b) => (new Date(b.created_at)).getTime() - (new Date(a.created_at)).getTime()).forEach(qs => {\n if (!tempSeenUsers.find(qs2 => (qs2.email === qs.user.email))) {\n tempSeenUsers.push(qs.user)\n }\n })\n \n setQuerySeenUsers(tempSeenUsers)\n }))\n }, [])\n const edited = useMemo(() => {\n return querySeen.find(qs => qs.action_type === 4) || querySeen.find(qs => qs.action_type === 1)\n }, [querySeen])\n\n const content = useMemo(() => {\n return {\n created_at: query_created_at,\n user_first_name: query_owner_username.split(' ')[0],\n user_last_name: query_owner_username.split(' ')[1],\n }\n }, [querySeen])\n\n return (\n <>\n setShowViewIcon(true)}\n onMouseLeave={() => setShowViewIcon(false)}\n className=\"queryItem\"\n ref={queryItemRef}\n sx={{ cursor: disableOnClick || isEditingName || isEditingDescription || isEditingQuery ? 'default' : 'pointer', userSelect: 'none' }}\n >\n \n {/* */}\n
\n\n {(\n \n \n \n \n \n \n \n \n e.stopPropagation()}>\n {\n if (type.includes('history')) {\n MixedPanelEvent.queryHistoryCopyQuery(query);\n } else {\n MixedPanelEvent.queryCopyClicked(query, '', '');\n }\n ApiClient.post('queries/query_seen', { query_uid, action_type: 8 }, {\n isProtected: true,\n })\n\n toast('Query successfully copied to clipboard ', {\n type: 'success'\n });\n }}\n >\n \n \n \n \n \n {/* \n \n \n \n \n \n */}\n {!type.includes('history') && allowMetaEdit && (\n {\n setAnchorEl2(e.currentTarget);\n e.stopPropagation();\n }}\n >\n \n \n \n \n )}\n \n )}\n {false && !type.includes('history') && \n \n \n \n \n Save to Snippets\n \n \n \n }\n \n \n \n {type.includes('history') ? (\n \n \n Query History\n \n \n \n ) : (\n \n {RenderCondition(\n showHighlight,\n \n \n {highlightMap.title.map((part, idx) => (\n \n {part.text}\n \n ))}\n \n ,\n RenderCondition(\n allowMetaEdit && isEditingName,\n ,\n \n )\n )}\n\n \n \n {RenderCondition(\n showHighlight,\n \n {highlightMap.parentFolder.map((part, idx) => (\n \n {part.text}\n \n ))}\n ,\n {folderName} \n )}\n \n \n {RenderIf(\n isAuto,\n \n \n \n \n \n )}\n {RenderCondition(\n showHighlight,\n \n \n {highlightMap.description.map((part, idx) => (\n \n {part.text}\n \n ))}\n \n ,\n RenderCondition(\n allowMetaEdit && isEditingDescription,\n ,\n 50 ? meta?.query_description.substring(0, 50) + '...' : meta?.query_description} onClick={onQueryDescriptionClick}/>\n )\n )}\n \n \n t.tag)\n }\n preTags={tags}\n />\n \n \n )}\n \n {!type.includes('history') && (\n \n \n \n )}\n \n \n \n \n \n \n \n \n handleDeletion()}\n title={'Delete query?'}\n subTitle=\"Are you sure you want to delete this query?\"\n />\n onSelectMoveQueryFolder(folder.uid)}\n folders={folders}\n open={openMoveQueryModal}\n onClose={toggleMoveQueryModal}\n handleSubmit={() => {\n if (!selectedMoveQueryFolder) {\n selectedMoveQueryFolder = folders[0].uid;\n }\n ApiClient.post('queries/query_seen', { query_uid, action_type: 14 }, {\n isProtected: true,\n })\n moveQuery(selectedMoveQueryFolder, query_uid, folderId!, query_name);\n dispatch(folderSummaryThunk.index());\n dispatch(sharedFolderThunk.index());\n }}\n defaultValue={folderId!}\n />\n >\n );\n };\n\nexport default QueryItem;\n\nfunction stopPropagation(e) {\n e.stopPropagation();\n}\n","export function parseDescription(desc: string) {\n const features = { isAuto: false, val: '' };\n const isAutoGenerated = desc?.startsWith('AutoGenerated');\n if (isAutoGenerated) {\n const asStr = desc.split('AutoGenerated | ')?.[1];\n features.isAuto = true;\n features.val = asStr;\n } else features.val = desc;\n return features;\n}\n","import React from 'react';\nimport { styled, IconButton, IconButtonProps } from '@mui/material';\n\nexport const RepositoryStatesBtnsBox = styled('div')(() => {\n return {\n display: 'flex',\n alignItems: 'flex-end',\n marginRight: '12px',\n padding: '0px',\n height: '34px',\n borderRadius: '4px',\n columnGap: '4px'\n };\n});\n\nexport const RepoDisplayToggleIconBtn = styled(\n (props: IconButtonProps & { selected: boolean }) => \n)(({ selected, theme }) => {\n const {\n primary: { main },\n background,\n text,\n grey\n } = theme.palette;\n const backgroundColor = selected ? main : grey['300'];\n return {\n padding: '4px',\n borderRadius: '4px',\n backgroundColor,\n color: selected ? background.default : text.secondary,\n ':hover': {\n color: text.disabled,\n backgroundColor\n }\n };\n});\n","import React from 'react';\nimport { useAppRepositoryDisplayMode } from '../../store/sherloqAppConfig/selector';\nimport { RepositoryDisplayModeType } from '../../store/sherloqAppConfig/type';\nimport { RepoDisplayToggleIconBtn, RepositoryStatesBtnsBox } from './styled';\nimport ViewDayIcon from '@mui/icons-material/ViewDay';\nimport FormatListBulletedOutlinedIcon from '@mui/icons-material/FormatListBulletedOutlined';\nimport { CustomTooltipWithWrapper } from '../../atoms/CustomTooltip/withWrapper';\nimport MixedPanelEvent from '../../utils/analytics/mixPanel';\n\nexport default function () {\n const [displayMode, setDisplayMode] = useAppRepositoryDisplayMode();\n\n const toggleDisplayMode = (mode: RepositoryDisplayModeType) => {\n setDisplayMode(mode);\n MixedPanelEvent.displayViewToggleClick({ displayView: mode });\n };\n\n return (\n \n \n toggleDisplayMode('thumbnail')}\n selected={displayMode === 'thumbnail'}\n >\n \n \n \n \n toggleDisplayMode('list')}\n selected={displayMode === 'list'}\n >\n \n \n \n \n );\n}\n","import ApiClient from \"../../../lib/utils/apiClient\";\n\nexport const addTag = async (tag_name: string) => {\n return ApiClient.post(\n `tags/`,\n { tag_name },\n {\n isProtected: true,\n },\n );\n};\n\nexport const getTags = async () => {\n return ApiClient.get(`tags/`, {\n isProtected: true,\n });\n};\n\nexport const deleteTag = async (tag_id: number) => {\n return ApiClient.delete(`tags/${tag_id}`, {\n isProtected: true,\n });\n};\n\nexport const addQueryTag = async (query_uid: string, tag_id: number) => {\n return ApiClient.post(\n `tags/query`,\n { tag_id, query_uid },\n {\n isProtected: true,\n },\n );\n};\n\nexport const getQueryTags = async (query_uid: string) => {\n return ApiClient.get(`tags/query/${query_uid}`, {\n isProtected: true,\n });\n};\n\nexport const deleteQueryTag = async (query_uid: string, tag_id: number) => {\n return ApiClient.delete(`tags/query/${tag_id}/${query_uid}`, {\n isProtected: true,\n });\n};","import createSvgIcon from './utils/createSvgIcon';\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon( /*#__PURE__*/_jsx(\"path\", {\n d: \"m21.41 11.41-8.83-8.83c-.37-.37-.88-.58-1.41-.58H4c-1.1 0-2 .9-2 2v7.17c0 .53.21 1.04.59 1.41l8.83 8.83c.78.78 2.05.78 2.83 0l7.17-7.17c.78-.78.78-2.04-.01-2.83zM6.5 8C5.67 8 5 7.33 5 6.5S5.67 5 6.5 5 8 5.67 8 6.5 7.33 8 6.5 8z\"\n}), 'Sell');","import createSvgIcon from './utils/createSvgIcon';\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon( /*#__PURE__*/_jsx(\"path\", {\n d: \"M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\"\n}), 'Clear');","import { toast } from 'react-toastify';\nimport DeleteModal from '../modals/DeleteModal';\nimport React, { useEffect, useState } from 'react';\nimport { Sell, Clear, HighlightOff } from '@mui/icons-material';\nimport { Box, Chip, Input, Menu, Button } from '@mui/material';\nimport { shortenString } from '../../pages/Editor/helpers/helpers';\nimport MixedPanelEvent from '../../utils/analytics/mixPanel';\nimport {\n addQueryTag,\n addTag,\n getQueryTags,\n getTags,\n deleteQueryTag,\n deleteTag\n} from './API/fetch';\nimport { newQueryFolderContentThunks } from '../../store/newFolderContent/thunk';\nimport { useAppDispatch } from '../../store';\n\ninterface Tag {\n id: number;\n uid: string;\n name: string;\n color: string;\n}\n\ninterface TagsProps {\n query_uid: string;\n preTags?: Tag[];\n preQueryTags?: Tag[];\n folder_id?: string;\n}\n\nconst Tags = ({ query_uid, preTags, preQueryTags, folder_id }: TagsProps) => {\n const dispatch = useAppDispatch();\n const [tag, setTag] = useState('');\n const [tags, setTags] = useState([]);\n const [currTag, setCurrTag] = useState();\n const [anchorEl, setAnchorEl] = useState(null);\n const [queryTags, setQueryTags] = useState([]);\n const [deleteTagModal, setDeletetagModal] = useState(false);\n\n useEffect(() => {\n if (preQueryTags) {\n setQueryTags(preQueryTags);\n } else {\n getQueryTags(query_uid).then(res => {\n setQueryTags(res.data.tags);\n });\n }\n if (preTags) {\n setTags(preTags);\n } else {\n getTags().then(res => {\n setTags(res.data.tags);\n });\n }\n }, [preTags]);\n\n const onKeyDown = (e: any) => {\n if (e.key == 'Enter' && tag.length > 1) {\n addTag(tag).then(res => {\n setTag('');\n if (res?.data?.id) {\n setTags([...tags, res.data]);\n }\n });\n MixedPanelEvent.addTagAction(tag);\n }\n };\n\n const addQueryTagHandler = (t: Tag) => {\n setAnchorEl(null);\n addQueryTag(query_uid, t.id).then(res => {\n if (res?.data?.id) {\n setQueryTags([...queryTags, t]);\n }\n });\n dispatch(newQueryFolderContentThunks.index({ uid: folder_id }));\n // MixedPanelEvent.addQueryTagAction(t.name);\n };\n\n const deleteQueryTagHandler = (t: Tag) => {\n deleteQueryTag(query_uid, t.id).then(() => {\n setQueryTags(queryTags.filter(qt => qt.id !== t.id));\n dispatch(newQueryFolderContentThunks.index({ uid: folder_id }));\n });\n };\n\n const deleteTagHandler = () => {\n MixedPanelEvent.deleteQueryTagAction(currTag?.name);\n setDeletetagModal(false);\n deleteTag(currTag?.id!).then(() => {\n toast.info('The tag and all its relationships deleted successfully', {\n type: 'success'\n });\n setTags(tags.filter(qt => qt.id !== currTag?.id!));\n setQueryTags(queryTags.filter(qt => qt.id !== currTag?.id!));\n });\n };\n\n return (\n \n \n {queryTags.map(t => (\n deleteQueryTagHandler(t)}\n deleteIcon={\n \n }\n />\n ))}\n {\n setAnchorEl(e.currentTarget);\n }}\n sx={{ margin: '2px', height: '22px', fontSize: '11px' }}\n deleteIcon={ }\n id=\"tagsMenu\"\n aria-controls={!!anchorEl ? 'tagsMenu' : undefined}\n aria-haspopup=\"true\"\n aria-expanded={!!anchorEl ? 'true' : undefined}\n onClick={e => {\n setAnchorEl(e.currentTarget);\n e.stopPropagation();\n }}\n />\n \n \n setDeletetagModal(false)}\n handleDelete={() => deleteTagHandler()}\n />\n \n );\n};\n\nexport default Tags;\n","import { Button, ButtonProps } from '@mui/material';\nimport React from 'react';\nimport { sx } from '../../../styles/MUI/styles';\nimport { isDatagrip } from '../../../utils/checkOrigin';\n\nexport const CancelButton = (props: Omit) => {\n return (\n \n Cancel\n \n );\n};\n","import { sx } from '../../styles/MUI/styles';\nimport React from 'react';\nimport { Box, Button, Typography } from '@mui/material';\nimport { ModalRoot } from './root';\nimport { DesignSystemMap } from '../../styles/MUI/designSystem';\nimport { CancelButton } from './components/cancelButton';\nimport { ModalTypography } from './components/label';\n\ninterface DeleteModalProps {\n title: string;\n subTitle: string;\n show: boolean;\n setClose: () => void;\n handleDelete: () => void;\n}\nDesignSystemMap;\nconst DeleteModal = ({\n title,\n subTitle,\n show,\n setClose,\n handleDelete\n}: DeleteModalProps) => {\n return (\n \n {title} \n \n {subTitle} \n \n \n \n Delete\n \n \n \n \n );\n};\n\nexport default DeleteModal;\n","import './index.scss';\nimport React from 'react';\nimport { sx } from '../../styles/MUI/styles';\nimport { Box, Button, IconButton } from '@mui/material';\nimport { ModalRoot } from './root';\nimport { CancelButton } from './components/cancelButton';\nimport { ModalTypography } from './components/label';\nimport { Input } from '../Input';\nimport { RenderIf } from '../../lib/utils';\nimport { SherloqButton } from '../../v2/components/common/SherloqButton';\nimport CopyLinkIcon from '../../v2/components/icons/CopyLink';\nimport CopyToClipboard from 'react-copy-to-clipboard';\nimport { trimEnd } from 'lodash';\nimport MixedPanelEvent from '../../utils/analytics/mixPanel';\nimport { useSherloqToast } from '../../v2/components/common/SherloqToastProvider';\n\ninterface InviteFeedbackModalProps {\n title: string;\n subTitle: string;\n show: boolean;\n text: string;\n setText: any;\n placeholder: string;\n setClose: () => void;\n handlerSubmit: () => void;\n showCancel?: boolean;\n saveButtonText?: string;\n rows?: number;\n}\n\nconst InviteFeedbackModal = ({\n title,\n subTitle,\n show,\n setClose,\n handlerSubmit,\n text,\n setText,\n placeholder,\n showCancel = true,\n saveButtonText = 'Invite',\n rows = 2\n}: InviteFeedbackModalProps) => {\n const toast = useSherloqToast()\n const getShareableQueryLink = () => {\n let domain = `${window.location.protocol}//${window.location.host}`;\n domain = trimEnd(domain, '/');\n return `${domain}/?invite`;\n }\n return (\n \n {title} \n \n {subTitle} \n \n setText(e)}\n rows={rows}\n />\n \n \n \n { MixedPanelEvent.extensionSettingInviteByLinkClicked({}); toast('Invitation link has been copied to your clipboard.', 'info')}}>\n \n \n \n \n Copy Link\n \n \n {RenderIf(showCancel, Cancel )}\n \n {saveButtonText}\n \n \n \n \n );\n};\n\nexport default InviteFeedbackModal;\n","import React from 'react';\n\nimport { useDarkModeState } from '../../store/themeMode/selectors';\n\nconst CloseIcon = () => {\n const darkMode = useDarkModeState();\n return (\n \n \n \n \n );\n};\n\nexport default CloseIcon;\n","import React from 'react';\nimport { Box, Typography, IconButton, Button } from '@mui/material';\nimport CheckIcon from '@mui/icons-material/Check';\nimport ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';\nimport ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';\nimport { ConditionComponent } from '../../lib/utils';\nimport CopyToClipboard from 'react-copy-to-clipboard';\nimport { ContentCopyRounded } from '@mui/icons-material';\nimport { formatSql } from '../../utils/formaters';\nconst QueryModalButtons = ({\n handlePrevious,\n handleNext,\n currentIndex,\n data,\n showCopySuccess,\n handleCopy\n}) => {\n return (\n \n 1}>\n \n \n \n\n {`${currentIndex + 1} / ${data.length}`} \n\n \n \n \n \n 0 && data[currentIndex].query)}\n onCopy={() => handleCopy()}\n >\n \n \n \n \n \n \n \n\n \n {showCopySuccess ? 'Copied' : 'Copy'}\n \n \n \n \n );\n};\n\nexport default QueryModalButtons;\n","import React, { useState } from 'react';\nimport { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';\nimport { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism';\nimport {\n Box,\n Typography,\n List,\n ListItem,\n IconButton,\n Link,\n Divider\n} from '@mui/material';\nimport CloseIcon from '../Icons/CloseIcon';\nimport { useDarkModeState } from '../../store/themeMode/selectors';\nimport { ConditionComponent } from '../../lib/utils';\nimport { formatSql } from '../../utils/formaters';\nimport QueryModalButtons from './QueryModalButtons';\nimport MixedPanelEvent from '../../utils/analytics/mixPanel';\n\nconst ShowMoreDataModal = ({\n show,\n setClose,\n data,\n dataType,\n title = '',\n subtitle = ''\n}: any) => {\n if (!show || (dataType === 'query' && data.length == 0)) {\n return null;\n }\n const darkMode = useDarkModeState();\n const [currentIndex, setCurrentIndex] = useState(0);\n const [showCopySuccess, setShowCopySuccess] = useState(false);\n const handleCopy = () => {\n MixedPanelEvent.watsonDashboardSqlCopied({ data });\n setShowCopySuccess(true);\n setTimeout(() => {\n setShowCopySuccess(false);\n }, 1500);\n };\n const handleNext = () => {\n setCurrentIndex(prevIndex => (prevIndex + 1) % data.length);\n };\n\n const handlePrevious = () => {\n setCurrentIndex(prevIndex => (prevIndex - 1 + data.length) % data.length);\n };\n\n return (\n // Shadow behind\n \n {/* Modal Box */}\n e.stopPropagation()}\n >\n {/* Buttons Box */}\n \n \n \n \n \n \n \n \n \n \n {title\n .replace(/([A-Z])/g, ' $1')\n .replace(/^./, str => str.toUpperCase())}\n \n \n \n \n \n {title}\n \n \n For modifying go to \"Customized Suggestions\" tab\n \n \n \n\n \n \n \n {Array.isArray(data) &&\n data.map((item: any, index: any) => (\n \n {item} \n \n ))}\n
\n \n \n {Array.isArray(data) &&\n data.map(\n (feature: {\n description: string;\n name: string;\n suggestions: string[];\n }) => (\n \n \n {feature.name}\n \n {Array.isArray(feature.suggestions) &&\n feature.suggestions.map(suggestion => (\n \n \n {suggestion}\n \n \n \n ))}\n \n )\n )}\n \n \n {data} \n \n \n \n \n \n In this option, you can choose where you want to show Sherloq\n suggestions.\n \n \n Auto-complete means while writing into your IDE on the\n 'WHERE' or 'JOIN' clauses. The suggestion will be based on the\n specific tables noted in the query before the clause.\n \n \n \"Post run\" refers to the suggestion from Sherloq that\n appears after running a query. The suggestion will be\n regarding the tables noted in the query run/console page and\n will be shown as a pop-up\n \n \n \n \n \n \n The default shown values are based on the common usage of all\n your table filters in your organization . If you want to\n adopt customized suggestions, add it in the{' '}\n \"customize suggestion\" . \n industry_type = ‘commerce’ \n \n \n No Display values Displaying the field name that\n is commonly filtered on the particular table: \n industry_type \n \n \n \n \n \n \n This option allows you to see partition fields in the\n suggestions even if they are not used in your organization.\n For instance, if the partition field for order table is date,\n Sherloq's suggestions will display as follows: \n order.date = ‘YYYY-MM-DD’ \n \n \n No display partition fields Only the partition\n fields that your organization has used will be displayed in\n the suggestions\n \n \n \n \n \n \n In this section, you can add or edit your customized\n preferences: \n 1. Add new customized suggestions for the 'WHERE'\n clause or post-run a query. Here’s an example of a customized\n suggestion: \n order.type_user IN ('electricity', 'nature') \n \n \n 2. Edit an existing suggestion \n For instance, if your current suggestion is\n order.user_id = 5332, \n you can modify it as follows: \n order.user_id {'>'} 0 \n \n \n \n \n \n \n In this section, you can view all of Sherloq's suggestions\n based on your preferences. \n To add a new suggestion or edit an existing one, you can do so\n in the \"Customized Suggestions\" tab. \n After modifying your preferences, our algorithm will update\n within an hour. You will receive an email notification about\n these updates.\n \n \n \n \n \n \n {Array.isArray(data) &&\n data.map((item: any, index: any) => (\n \n \n \n {item.name}\n \n \n \n {item.name} \n \n \n Owner: {item?.owner?.split('@')[0]}\n \n \n {`# of views: ${item.views}`} \n \n \n Folder: {item.project}\n \n \n ))}\n
\n \n 0 && dataType === 'query'}\n >\n 0 && data[currentIndex].query\n )}\n style={{ ...atomDark }}\n language={'sql'}\n PreTag=\"div\"\n wrapLongLines={true}\n codeTagProps={{\n style: {\n maxWidth: '100%',\n fontSize: '11px',\n width: 'inherit',\n display: 'inline-block',\n overflowWrap: 'break-word',\n transition: 'max-height 2s ease'\n }\n }}\n />\n \n \n \n \n );\n};\n\nexport default ShowMoreDataModal;\n","import { styled, Typography } from '@mui/material';\nexport const ModalTypography = styled(Typography)(({ theme }) => {\n return {\n color: theme.palette.primary.dark\n };\n});\n","import React from 'react';\nimport { Typography } from '@mui/material';\nimport { isDatagrip } from '../../../utils/checkOrigin';\n\ninterface FilterLabelProps {\n children: React.ReactNode;\n}\n\nexport const FilterLabel = ({ children }: FilterLabelProps) => {\n return (\n \n \n {children}\n \n
\n );\n};\n","import React, { useMemo, useState } from 'react';\nimport {\n Typography,\n Box,\n styled,\n SelectChangeEvent,\n Chip\n} from '@mui/material';\nimport { useUserInfoSelector } from '../../../store/auth/selectors';\nimport { FilterLabel } from '../../../pages/SearchFilter/components/filterLabel';\nimport { useRenderTeamSelectorValues } from '../hooks/useRenderTeamSelectorValues';\nimport { useTeamMembersSelector } from '../../../store/teamMembers/selectors';\nimport UserAvatar from '../../../v2/components/common/UserAvatar';\nimport SherloqDropdown from '../../../v2/components/common/SherloqDropdown';\nimport { useInternalTeamsSelector } from '../../../store/internalTeams/selectors';\nimport MembersAutocomplete from '../../../v2/components/common/MembersAutocomplete';\nimport MixedPanelEvent from '../../../utils/analytics/mixPanel';\n\ninterface TeamMembersSelector {\n selectedMembers: { email: string; permissions: string }[];\n onClickChip: (val: any) => void;\n creatorEmail?: string;\n setSelectedMembers: any;\n selectedTeams: any[];\n setSelectedTeams: any;\n existingMembers?: any[];\n existingTeams?: any[];\n inheritedMembers?: any[];\n inheritedTeams?: any[];\n withTeams?: boolean;\n withInherited?: boolean;\n userPermissions: 'viewer' | 'editor' | 'owner';\n}\n\nconst DropdownsContainer = styled(Box)`\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n gap: 12px;\n margin-bottom: 16px;\n >div {\n width: 100%;\n }\n`\n\nconst TeamAvatarsContainer = styled(Box)`\n position: relative;\n width: 26px;\n height: 26px;\n`\n\nconst TagsContainer = styled(Box)`\n margin-top: 12px;\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n width: 100%;\n`\n\nconst ListContainer = styled(Box)<{ $withInherited?: boolean }>`\n max-height: ${({ $withInherited }) => $withInherited ? '120px' : '200px'};\n overflow-y: auto;\n margin-top: 6px;\n margin-bottom: 12px;\n display: flex;\n flex-direction: column;\n gap: 0;\n width: 100%;\n border-radius: 8px;\n padding: 8px 0;\n background: ${({theme}) => theme.palette.background.paper};\n\n /* For Firefox */\n scrollbar-width: thin;\n scrollbar-color: ${({theme}) => `${theme.palette.grey[400]} transparent`};\n\n /* For Webkit browsers (Chrome, Safari, etc.) */\n &::-webkit-scrollbar {\n width: 6px;\n }\n\n &::-webkit-scrollbar-track {\n background: transparent;\n }\n\n &::-webkit-scrollbar-thumb {\n background-color: ${({theme}) => theme.palette.grey[400]};\n border-radius: 3px;\n }\n\n &::-webkit-scrollbar-thumb:hover {\n background-color: ${({theme}) => theme.palette.grey[500]};\n }\n`\n\nconst ListItemRow = styled(Box)`\n display: flex;\n align-items: center;\n padding: 2px 20px;\n`\n\nconst UserInfo = styled(Box)`\n display: flex;\n flex-direction: column;\n margin-left: 18px;\n flex: 1;\n`\n\nconst TeamUserInfo = styled(UserInfo)`\n margin-left: 16px;\n`\n\nconst UserName = styled(Typography)`\n font-size: 14px;\n color: ${({theme}) => theme.palette.text.primary};\n`\n\nconst UserEmail = styled(Typography)`\n font-size: 12px;\n color: ${({theme}) => theme.palette.text.secondary};\n`\n\nconst PermissionCell = styled(Box)`\n min-width: 70px;\n display: flex;\n justify-content: flex-end;\n`\n\nconst InheritedLabel = styled(Typography)`\n font-size: 12px;\n color: ${({theme}) => theme.palette.text.secondary};\n margin-bottom: 8px;\n`;\n\nexport const CloseSvg = () => (\n \n \n \n \n\n)\n\nexport const TeamAvatars = ({ members }) => (\n \n {members.slice(0, 2).map((member, index) => (\n \n ))}\n \n)\n\nexport const TeamMembersSelector = ({\n setSelectedMembers,\n onClickChip,\n selectedMembers,\n selectedTeams,\n setSelectedTeams,\n creatorEmail,\n existingMembers = [],\n existingTeams = [],\n inheritedMembers = [],\n inheritedTeams = [],\n withTeams = false,\n withInherited = false,\n userPermissions\n}: TeamMembersSelector) => {\n const user = useUserInfoSelector();\n const { list: teamMembers } = useTeamMembersSelector();\n\n const uniqueEmails = creatorEmail && !withInherited ? Array.from(new Set([...selectedMembers.map(m => m.email), creatorEmail])) : selectedMembers.map(m => m.email);\n\n const selectedMembersFull = uniqueEmails.map(email => {\n const existingMember = selectedMembers.find(m => m.email === email) || \n teamMembers.find(tm => tm.email === email);\n if (existingMember) {\n return { \n ...existingMember, \n permissions: existingMember.permissions || 'viewer' \n };\n }\n return { email, first_name: '', last_name: '', picture: '', permissions: 'viewer' };\n }).sort((a, b) => {\n // Put creator first\n if (a.email === creatorEmail) return -1;\n if (b.email === creatorEmail) return 1;\n return 0;\n });\n\n const getPermissionOptions = (isOwner: boolean) => {\n const options = [\n { value: 'viewer', label: 'Viewer', isSelected: false },\n { value: 'editor', label: 'Editor', isSelected: false },\n ];\n\n if (userPermissions === 'owner') {\n options.push({ value: 'owner', label: 'Owner', isSelected: false });\n options.push({ value: 'remove', label: 'Remove Access', isSelected: false });\n }\n\n return options;\n };\n\n const capitalizeFirstLetter = (string: string) => {\n return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();\n };\n\n const handlePermissionChange = (member, newPermission) => {\n if (newPermission === 'remove') {\n onClickChip(member.email);\n return;\n }\n \n // Update the member's permission\n const updatedMembers = selectedMembersFull.map(m => \n m.email === member.email ? { ...m, permissions: newPermission } : m\n );\n setSelectedMembers(updatedMembers.map(m => ({\n ...m,\n permissions: m.permissions\n })));\n };\n\n const onSelectMembers = (members) => {\n if (members.length > selectedMembers.length) {\n MixedPanelEvent.extensionAddMemberToShareFolderModal({})\n } else {\n MixedPanelEvent.extensionRemoveMemberFromShareFolderModal({})\n }\n // Preserve existing permissions when selecting members\n const updatedMembers = members.map(member => ({\n ...member,\n permissions: member.permissions || (userPermissions === 'viewer' ? 'viewer' : 'editor')\n }));\n\n setSelectedMembers(updatedMembers);\n }\n\n const onSelectTeams = (teams) => {\n const updatedTeams = teams.map(team => ({\n ...team,\n permissions: team.permissions || (userPermissions === 'viewer' ? 'viewer' : 'editor')\n }));\n\n setSelectedTeams(updatedTeams);\n }\n\n return (\n \n Select who to share with \n \n \n \n Shared with \n {withInherited && (inheritedMembers.length > 0 || inheritedTeams.length > 0) && (\n <>\n Parent folder permissions: \n \n {inheritedMembers.map((member, i) => (\n \n \n \n \n {member.first_name \n ? `${member.first_name} ${member.last_name}`\n : member.email\n }\n \n {member.email} \n \n \n \n {member.email === creatorEmail ? (\n Owner \n ) : capitalizeFirstLetter(member.permissions)}\n \n \n \n ))}\n {inheritedTeams.map((team, i) => (\n \n team.members.find(m => m.user_email === tm.email)).slice(0, 2)} />\n \n {team.name} \n {team.members?.length || 0} members \n \n \n \n {capitalizeFirstLetter(team.permissions)}\n \n \n \n ))}\n \n >\n )}\n {(selectedMembersFull.length > 0 || selectedTeams.length > 0) && withInherited && Query permissions (override folder permissions): }\n {(selectedMembersFull.length > 0 || selectedTeams.length > 0) && \n {selectedMembersFull.map((member, i) => (\n \n \n \n \n {member.first_name \n ? `${member.first_name} ${member.last_name}${creatorEmail === member.email ? ' (Creator)' : member.email === user?.email ? ' (You)' : ''}`\n : `${member.email} (pending)`\n }\n \n {member.first_name && {member.email} }\n \n \n {member.email === creatorEmail || userPermissions === 'viewer' ? (\n \n {member.email === creatorEmail ? 'Owner' : capitalizeFirstLetter(member.permissions || 'viewer')}\n \n ) : (\n handlePermissionChange(member, e.target.value)}\n sx={{ minWidth: '100px' }}\n optionsWithDivider={['remove']}\n noBorder\n disabled={userPermissions === 'editor' && member.permissions === 'owner'}\n />\n )}\n \n \n ))}\n {selectedTeams.map((team, i) => (\n \n team.members.find(m => m.user_email === tm.email)).slice(0, 2)} />\n \n {team.name} \n {team.members.length} members \n \n \n {userPermissions === 'viewer' ? (\n \n {capitalizeFirstLetter(team.permissions || 'viewer')}\n \n ) : (\n {\n if (e.target.value === 'remove') {\n setSelectedTeams(teams => teams.filter(t => t.id !== team.id));\n return;\n }\n setSelectedTeams(teams => \n teams.map(t => t.id === team.id ? { \n ...t, \n permissions: e.target.value \n } : t)\n );\n }}\n sx={{ minWidth: '100px' }}\n noBorder\n />\n )}\n \n \n ))}\n }\n \n );\n};\n","/**\n * Recursively finds a folder by its UID in a folder tree structure\n * @param folders Array of folder objects that may contain sub_folders\n * @param uid The unique identifier of the folder to find\n * @returns The found folder object or null if not found\n */\nexport function findFolderByUid(folders, uid) {\n for (const folder of folders) {\n if (folder.uid === uid) return folder;\n\n if (folder.sub_folders && folder.sub_folders.length > 0) {\n const result = findFolderByUid(folder.sub_folders, uid);\n if (result) return result;\n }\n }\n return null;\n}\n\n/**\n * Finds the root parent folder of a given folder UID in a folder tree structure\n * @param foldersArray Array of folder objects that may contain sub_folders\n * @param targetUid The unique identifier of the folder whose root parent we want to find\n * @param rootUid Optional root UID to fall back to if target folder is not found\n * @returns The root parent folder object or null if not found\n */\nexport function findFolderRootParentByUid(foldersArray, targetUid, rootUid?: string) {\n // Helper function to recursively find the topmost parent\n function findRootParent(folders, folder) {\n if (!folder.parent_uid) return folder; // This is the root parent\n\n const parent = findFolderByUid(folders, folder.parent_uid);\n return parent ? findRootParent(folders, parent) : folder;\n }\n\n // Step 1: Find the initial target folder\n const targetFolder = findFolderByUid(foldersArray, targetUid);\n\n // Step 2: If found, find its root parent\n return targetFolder ? \n findRootParent(foldersArray, targetFolder) : \n (rootUid ? findFolderRootParentByUid(foldersArray, rootUid, null) : null);\n} ","import React, { useEffect, useState } from 'react';\nimport { ModalRoot, ModalRootProps } from './root';\nimport { Box, styled } from '@mui/material';\nimport { ModalTypography } from './components/label';\nimport { Typography } from '../../atoms/Typography';\nimport SherloqDropdown from '../../v2/components/common/SherloqDropdown';\nimport { TreeDropdownContext } from '../selectFolderDropdown/context';\nimport { CustomTooltip } from '../../atoms/CustomTooltip';\nimport { SherloqButton } from '../../v2/components/common/SherloqButton';\nimport Loader from '../../lib/components/Loader/Loader';\nimport { calculateUserPermissions } from '../../utils/permissions';\nimport { useUserInfoSelector } from '../../store/auth/selectors';\nimport { useInternalTeamsSelector } from '../../store/internalTeams/selectors';\nimport { findFolderByUid, findFolderRootParentByUid } from '../../utils/folderUtils';\n\ninterface MoveQueryModalProps extends Pick {\n dropdownProps?: Omit;\n folders?: any;\n value?: any;\n queryName?: string;\n onChange: any;\n defaultValue: string;\n handleSubmit: Function;\n isLoading?: boolean;\n isFolder?: boolean;\n}\n\nconst LoaderContainer = styled(Box)`\n div {\n margin-top: 0;\n }\n span {\n width: 20px !important;\n height: 20px !important;\n }\n`\n\nexport const MoveQueryModal: React.FC = ({\n folders,\n queryName,\n value,\n onChange,\n isLoading,\n handleSubmit,\n isFolder,\n onClose,\n ...props\n}: MoveQueryModalProps) => {\n const userInfo = useUserInfoSelector();\n const { list: teams } = useInternalTeamsSelector();\n const [selectedFolder, setSelectedFolder] = useState('');\n\n const selectedFolderData = selectedFolder ? findFolderByUid(folders, selectedFolder) : null;\n const topParentFolder = selectedFolder ? findFolderRootParentByUid(folders, selectedFolder) : null;\n \n const userPermissions = selectedFolderData ? \n calculateUserPermissions(selectedFolderData, userInfo, teams, topParentFolder) : 'owner';\n \n const isViewer = userPermissions === 'viewer';\n\n const handleItemClick = (folder: any) => {\n setSelectedFolder(folder.uid);\n };\n\n useEffect(() => {\n onChange(selectedFolder)\n }, [selectedFolder])\n\n return (\n \n Move {isFolder ? 'folder' : 'query'} '{queryName}' \n \n \n Keep your queries organized with folders\n \n \n \n {}}\n folders={folders}\n options={[]}\n title='Select folder'\n />\n \n \n \n {/* \n Note:\n \n Shared folders are visible to the entire team\n */}\n \n \n \n Cancel\n \n \n \n \n {isLoading ? : isFolder ? 'Move Folder' : 'Move Query'}\n \n \n \n \n \n \n \n );\n};\n","import { styled, Box, BoxProps } from '@mui/material';\n\ninterface ModalContainerProps extends BoxProps {\n variant: 'default' | 'error';\n}\n\nexport const ModalContainer = styled(Box)(\n ({ theme, variant }) => {\n const {\n text: { primary },\n error: { main },\n primary: { main: defaultColor },\n grey\n } = theme.palette;\n const borderBottomColor = variant === 'error' ? main : defaultColor;\n return {\n backgroundColor: theme.palette.background.default,\n width: 'calc(100% - 40px)',\n maxWidth: '450px',\n boxSizing: 'border-box',\n padding: '14px',\n display: 'flex',\n flexDirection: 'column',\n flex: '0 1 auto',\n borderRadius: '8px',\n fontFamily: 'Roboto, Helvetica, Arial, sans-serif',\n color: primary,\n border: `1px solid ${grey['100']}`,\n position: 'fixed',\n top: '50%',\n left: '50%',\n transform: 'translate(-50%, -50%)',\n height: 'fit-content'\n };\n }\n);\n","import React from 'react';\nimport { Modal, ModalProps, BoxProps, Button } from '@mui/material';\nimport ClearIcon from '@mui/icons-material/Clear';\nimport { ModalContainer } from './components/container';\n\nexport interface ModalRootProps extends Omit {\n containerProps?: BoxProps;\n variant: 'default' | 'error';\n blurredBG?: boolean;\n children: React.ReactNode | React.ReactNode[];\n}\n\n/**\n *\n * @param param0\n * id sherloq_root keeps the modal within the sherloq_\n */\nexport const ModalRoot = ({\n children,\n containerProps,\n variant,\n ...props\n}: ModalRootProps) => {\n const handleOnClose = ev => props?.onClose?.(ev, 'backdropClick');\n return (\n \n \n {children}\n {props.onClose != null && \n \n }\n \n \n );\n};\n","import createSvgIcon from './utils/createSvgIcon';\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport default createSvgIcon( /*#__PURE__*/_jsx(\"path\", {\n d: \"m10 17 5-5-5-5v10z\"\n}), 'ArrowRight');","import React from 'react';\nimport { MenuItem, Typography, Box, Stack, BoxProps } from '@mui/material';\nimport { ArrowRight } from '@mui/icons-material';\nimport { RenderIf } from '../../../lib/utils';\nimport FolderIcon from '../../../v2/components/icons/Folder'\nimport { CustomTooltip } from '../../../atoms/CustomTooltip';\nimport { FolderType } from '../../../store/folderSummary/type';\nimport {\n dropdownFolderTypeIconStyles,\n dropdownItemSummaryTooltipStyles,\n dropdownListItemSummaryContainer,\n dropdownListItemSummaryMenuItemContainer,\n dropdownListItemSummaryMenuItemIconStyles,\n dropdownListItemSummaryMenuItemStackStyles\n} from '../styles';\nimport FolderSharedIcon from '../../../v2/components/icons/FolderShared';\nimport FolderPublicIcon from '../../../v2/components/icons/FolderPublic';\n\ninterface DrodownFolderItemDataType {\n folderType: FolderType;\n label: string;\n hasContent: boolean;\n}\n\ninterface DropdownFolderProps extends BoxProps {\n data: DrodownFolderItemDataType;\n isSelected?: boolean;\n}\n\nexport const DropdownMenuListItemSummary = React.forwardRef(\n (\n { data, isSelected, ...props }: DropdownFolderProps,\n ref: React.MutableRefObject\n ) => {\n const showTooltip = data?.label?.length > 32;\n return (\n \n \n \n \n \n {data?.folderType === 'personal' ? \n :\n data?.folderType === 'shared' ? \n :\n }\n \n \n {data?.label}\n \n \n\n {RenderIf(\n data?.hasContent,\n \n \n \n )}\n \n \n \n );\n }\n);\n","import React from 'react';\nimport Menu, { MenuProps } from '@mui/material/Menu';\nimport { dropdownMenuPaperStyle } from '../styles';\n\ninterface DropdownMenuListItemSubMenuContainerProps extends MenuProps {}\n\nexport const DropdownMenuListItemSubMenuContainer = React.forwardRef(\n (\n { children, ...props }: DropdownMenuListItemSubMenuContainerProps,\n ref: React.MutableRefObject\n ) => {\n return (\n \n \n {children}\n
\n \n );\n }\n);\n","import React, { useState, useRef, useImperativeHandle } from 'react';\nimport Menu, { MenuProps } from '@mui/material/Menu';\nimport { MenuItemProps } from '@mui/material/MenuItem';\nimport ArrowRight from '@mui/icons-material/ArrowRight';\nimport { DropdownMenuListItemSummary } from './components/dropdownFolder';\nimport { DropdownMenuListItemSubMenuContainer } from './components/dropdownMenuListItemSubMenuContainer';\nimport { useSelectItemInTreeDropdown } from './context';\nimport { debounce } from 'lodash';\n\nexport interface NestedMenuItemProps extends Omit {\n /**\n * Open state of parent ` `, used to close decendent menus when the\n * root menu is closed.\n */\n parentMenuOpen: boolean;\n /**\n * Component for the container element.\n * @default 'div'\n */\n component?: React.ElementType;\n /**\n * Effectively becomes the `children` prop passed to the ` `\n * element.\n */\n label?: React.ReactNode;\n /**\n * @default \n */\n rightIcon?: React.ReactNode;\n /**\n * Props passed to container element.\n */\n ContainerProps?: React.HTMLAttributes &\n React.RefAttributes;\n /**\n * Props passed to sub ` ` element\n */\n MenuProps?: Omit;\n /**\n * @see https://material-ui.com/api/list-item/\n */\n button?: true | undefined;\n data?: any;\n onSelectFolder?: () => void;\n selectedFolders?: string[];\n}\n\n/**\n * Use as a drop-in replacement for `` when you need to add cascading\n * menu elements as children to this component.\n */\nconst NestedMenuItem = React.forwardRef<\n HTMLLIElement | null,\n NestedMenuItemProps\n>(function NestedMenuItem(props, ref) {\n const {\n parentMenuOpen,\n component = 'div',\n label,\n rightIcon = ,\n children,\n className,\n tabIndex: tabIndexProp,\n data,\n selectedFolders,\n MenuProps = {},\n ContainerProps: ContainerPropsProp = {},\n onSelectFolder,\n ...MenuItemProps\n } = props;\n const hasContent = data?.sub_folders.length > 0;\n const onSelectItem = useSelectItemInTreeDropdown();\n\n const { ref: containerRefProp, ...ContainerProps } = ContainerPropsProp;\n\n const menuItemRef = useRef(null);\n useImperativeHandle(ref, () => menuItemRef.current as HTMLLIElement);\n\n const containerRef = useRef(null);\n useImperativeHandle(containerRefProp, () => containerRef.current);\n\n const menuContainerRef = useRef(null);\n\n const [isSubMenuOpen, setIsSubMenuOpen] = useState(false);\n\n const openDropdown = debounce(\n event => {\n setIsSubMenuOpen(true);\n\n if (ContainerProps?.onMouseEnter) {\n ContainerProps.onMouseEnter(event);\n }\n },\n 300,\n { trailing: true, leading: false }\n );\n\n const handleMouseEnter = (event: React.MouseEvent) => {\n if (!hasContent) return;\n openDropdown.cancel();\n openDropdown(event);\n };\n const handleMouseLeave = (event: React.MouseEvent) => {\n openDropdown.cancel();\n setIsSubMenuOpen(false);\n\n if (ContainerProps?.onMouseLeave) {\n ContainerProps.onMouseLeave(event);\n }\n };\n\n // Check if any immediate children are active\n const isSubmenuFocused = () => {\n const active = containerRef.current?.ownerDocument?.activeElement;\n for (const child of menuContainerRef.current?.children ?? []) {\n if (child === active) {\n return true;\n }\n }\n return false;\n };\n\n const handleFocus = (event: React.FocusEvent) => {\n if (event.target === containerRef.current) {\n setIsSubMenuOpen(true);\n }\n\n if (ContainerProps?.onFocus) {\n ContainerProps.onFocus(event);\n }\n };\n\n const handleKeyDown = (event: React.KeyboardEvent) => {\n if (event.key === 'Escape') {\n return;\n }\n\n if (isSubmenuFocused()) {\n event.stopPropagation();\n }\n\n const active = containerRef.current?.ownerDocument?.activeElement;\n\n if (event.key === 'ArrowLeft' && isSubmenuFocused()) {\n containerRef.current?.focus();\n }\n\n if (\n event.key === 'ArrowRight' &&\n event.target === containerRef.current &&\n event.target === active\n ) {\n const firstChild = menuContainerRef.current?.children[0] as\n | HTMLElement\n | undefined;\n firstChild?.focus();\n }\n };\n\n const handleOnSelectFolder = () => {\n onSelectFolder();\n onSelectItem(data);\n };\n\n const open = isSubMenuOpen && parentMenuOpen;\n\n // Root element must have a `tabIndex` attribute for keyboard navigation\n let tabIndex;\n if (!props.disabled) {\n tabIndex = tabIndexProp !== undefined ? tabIndexProp : -1;\n }\n\n return (\n \n 0,\n label: data?.name\n }}\n />\n {\n setIsSubMenuOpen(false);\n }}\n >\n {children}\n \n
\n );\n});\n\nexport default NestedMenuItem;\n","import React from 'react';\nimport NestedMenuItem, { NestedMenuItemProps } from '../NestedMenu';\n\ninterface ListItemProps extends NestedMenuItemProps {\n data: any;\n selectedFolders?: string[];\n onSelectFolder?: () => void;\n}\n\nexport const DropdownMenuListItem = ({ data, selectedFolders, onSelectFolder, ...props }: ListItemProps) => {\n return (\n \n {data?.sub_folders?.map(subFolder => {\n return (\n \n );\n })}\n \n );\n};\n","import { createContext, useContext } from 'react';\n\nexport const TreeDropdownContext = createContext(null);\n\nexport const useSelectItemInTreeDropdown = () =>\n useContext(TreeDropdownContext);\n","import { SxProps } from '@mui/material';\n\nconst selectedFolderContainerStyles: SxProps = {\n textTransform: 'none',\n display: 'flex',\n flexDirection: 'row',\n alignItems: 'center',\n columnGap: '6px'\n};\n\nconst dropdownFolderTypeIconStyles: SxProps = {\n color: 'text.primary',\n height: '20px',\n width: '20px'\n};\n\nconst dropdownZindex = 9999;\nconst dropdownItemSummaryIndex = 99999;\n\nconst dropdownMenuPaperStyle = {\n maxWidth: '260px',\n maxHeight: '400px'\n};\n\nconst dropdownListItemSummaryContainer = {\n zIndex: 99999,\n display: 'flex',\n justifyContent: 'flex-start',\n alignItems: 'center',\n width: dropdownMenuPaperStyle.maxWidth,\n padding: 0\n};\n\nconst dropdownListItemSummaryMenuItemContainer = (isSelected?: boolean) => ({\n width: dropdownMenuPaperStyle.maxWidth,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: '10px',\n backgroundColor: isSelected ? 'grey.100' : ''\n});\n\nconst dropdownListItemSummaryMenuItemStackStyles = {\n width: `calc(${dropdownMenuPaperStyle.maxWidth} - 40px)`,\n columnGap: '4px',\n alignItems: 'center'\n};\n\nconst dropdownItemSummaryTooltipStyles = {\n zIndex: 999999\n};\n\nconst dropdownListItemSummaryMenuItemIconStyles = {\n // flexShrink: 0,\n alignItems: 'center',\n display: 'flex',\n position: 'relative'\n};\n\nconst selectedFolderIconStypes = {\n height: '16px',\n width: '16px'\n};\n\nconst selectedFolderTextDefaultStyles: any = {\n textOverflow: 'ellipsis',\n top: '1px',\n position: 'relative',\n textTransform: 'none'\n};\n\nexport {\n selectedFolderContainerStyles,\n dropdownFolderTypeIconStyles,\n dropdownZindex,\n dropdownListItemSummaryContainer,\n dropdownItemSummaryIndex,\n dropdownItemSummaryTooltipStyles,\n dropdownListItemSummaryMenuItemContainer,\n dropdownListItemSummaryMenuItemStackStyles,\n dropdownListItemSummaryMenuItemIconStyles,\n dropdownMenuPaperStyle,\n selectedFolderIconStypes,\n selectedFolderTextDefaultStyles\n};\n","import React from 'react';\nimport { UIStateType } from '.';\nimport { RootPageLayoutPropsContextType } from '../utils/types/page-layout';\n\nexport const UIStateContext = React.createContext(null);\nexport const ToggleDockStatePositionContext =\n React.createContext(null);\nexport const ToggleFrameVisibleContext = React.createContext(\n null\n);\nexport const ToggleSearchPanelVisibleContext =\n React.createContext(null);\nexport const ToggleSearchModalContext = React.createContext(\n null\n);\nexport const ToggleSaveQueryModalContext = React.createContext(\n null\n);\nexport const SetFrameVisibleContext = React.createContext(\n null\n);\nexport const ToggleSearchFiltersContext = React.createContext(\n null\n);\nexport const GetSearchFiltersContext = React.createContext(false);\nexport const RootPageLayoutPropsStateContext =\n React.createContext(null);\n","import React from 'react';\nimport {\n ToggleDockStatePositionContext,\n ToggleFrameVisibleContext,\n ToggleSearchPanelVisibleContext,\n UIStateContext,\n ToggleSearchModalContext,\n ToggleSaveQueryModalContext,\n SetFrameVisibleContext,\n ToggleSearchFiltersContext,\n GetSearchFiltersContext,\n RootPageLayoutPropsStateContext\n} from './contexts';\n\nexport const useUiState = () => React.useContext(UIStateContext);\nexport const useToggleDockPosition = () =>\n React.useContext(ToggleDockStatePositionContext);\nexport const useToggleFrame = () => React.useContext(ToggleFrameVisibleContext);\nexport const useToggleSearchPanel = () =>\n React.useContext(ToggleSearchPanelVisibleContext);\nexport const useToggleSearchModal = () =>\n React.useContext(ToggleSearchModalContext);\nexport const useToggleSaveQueryModal = () =>\n React.useContext(ToggleSaveQueryModalContext);\nexport const useOpenFrame = () => React.useContext(SetFrameVisibleContext);\nexport const useToggleSearchFiltersModal = () =>\n React.useContext(ToggleSearchFiltersContext);\nexport const useSearchFiltersContext = () =>\n React.useContext(GetSearchFiltersContext);\nexport const usePageLayoutPropsState = () =>\n React.useContext(RootPageLayoutPropsStateContext);\n","import { IUpdateAssetPayload } from '../utils/types';\n\nfunction updateAsset(payload: IUpdateAssetPayload) {\n if (payload.asset_created_date) {\n payload.asset_created_date = new Date(payload.asset_created_date);\n }\n\n return payload;\n}\n\nexport const ApiPayloadTransformer = Object.freeze({\n updateAsset\n});\n","import ApiClient from '../../lib/utils/apiClient';\nimport {\n IGenerateDescriptionPayloadResponse,\n IGetFieldInfoPayload,\n IGetFieldInfoPayloadResponse,\n IGetRecentQueriesPayload,\n IGetRecentQueriesPayloadResponse,\n IGetTableInfoPayload,\n IGetTableInfoPayloadResponse,\n IUpdateAssetPayload\n} from '../utils/types';\nimport { ApiPayloadTransformer } from './api.payload.transformer';\n\nconst getTableInfo = async (\n payload: IGetTableInfoPayload\n): Promise => {\n const path = '/analytics/table-info';\n const data = await ApiClient.post(path, payload, {\n isProtected: true\n });\n return data.data as IGetTableInfoPayloadResponse;\n};\n\nconst getFieldInfo = async (\n payload: IGetFieldInfoPayload\n): Promise => {\n const path = '/analytics/field-info';\n const data = await ApiClient.post(path, payload, {\n isProtected: true\n });\n return data.data as IGetFieldInfoPayloadResponse;\n};\n\nconst updateAsset = async (obj: IUpdateAssetPayload): Promise => {\n const path = '/analytics/update-asset-profile';\n const payload = ApiPayloadTransformer.updateAsset(obj);\n await ApiClient.post(path, payload, {\n isProtected: true\n });\n};\n\nconst generateDescription = async (\n payload: IUpdateAssetPayload\n): Promise => {\n const path = '/analytics/generate-description';\n const data = await ApiClient.post(path, payload, {\n isProtected: true\n });\n return data.data as IGenerateDescriptionPayloadResponse;\n};\n\nconst getRecentQueries = async (\n payload: IGetRecentQueriesPayload\n): Promise> => {\n const path = '/analytics/recent-queries';\n const data = await ApiClient.post(path, payload, {\n isProtected: true\n });\n return data.data;\n};\n\nexport const DataCatalogApiService = Object.freeze({\n getTableInfo,\n getFieldInfo,\n updateAsset,\n generateDescription,\n getRecentQueries\n});\n","export const dotenv = {\n appName: process.env.REACT_APP_APP_NAME!,\n mixPanelToken: process.env.REACT_APP_MIX_PANEL_TOKEN!,\n appEnv: process.env.REACT_APP_APP_ENV ?? 'development',\n posthogKey: process.env.REACT_APP_PUBLIC_POSTHOG_KEY!,\n posthogApiHost: process.env.REACT_APP_PUBLIC_POSTHOG_HOST!,\n darkModeToggle: process.env.REACT_APP_IS_DARK_MODE_TOGGLE_VISIBLE ?? 'false',\n isOnPremise: process.env.REACT_APP_IS_ON_PREMISE?.toLowerCase() === 'true'\n} as const;\n","import { IRoutingRule } from '../../lib/utils/interfaces';\nimport { useUserInfoSelector } from '../../store/auth/selectors';\n\nexport const useSimplyRoutingIntegration = (): IRoutingRule => {\n const user = useUserInfoSelector();\n const authorize = () => {\n if (user)\n return [\n 'nadav@sherloqdata.io',\n 'anthony@sherloqdata.io',\n 'hello@sherloqdata.com',\n 'hello@sherloqdata.io',\n 'yoav.arad@hellosimply.com'\n ].includes(user?.email);\n /**\n * on reload, user object is null so the true will allow it to pass the check as the state syncs with the app.\n * Need to investigate\n */\n return true;\n };\n\n return {\n authorize\n };\n};\n","import { useMemo } from 'react';\nimport { useUserOriginState } from '../store/userOrigin/selectors';\n\nexport const useEnvs = () => {\n const userOrigin = useUserOriginState();\n const isWebApp = useMemo(() => {\n try {\n return Boolean(window.parent.origin === window.location.origin);\n } catch (error) {\n return false;\n }\n }, []);\n const isDataGripExtension = userOrigin && userOrigin === 'datagrip';\n const isVSCodeExtension = userOrigin && userOrigin === 'vscode';\n\n return {\n isVSCodeExtension,\n isDataGripExtension,\n isChromeExtension: !isDataGripExtension && !isWebApp && !isVSCodeExtension\n };\n};\n","import React from 'react';\nimport { CircularProgress, Box } from '@mui/material';\nimport { useDarkModeState } from '../../../store/themeMode/selectors';\n\ninterface LoaderProps {\n loaderSize?: string;\n marginTop?: string;\n}\n\nconst Loader = ({ loaderSize, marginTop }: LoaderProps) => {\n const isDarkMode = useDarkModeState()\n\n return (\n \n \n \n );\n};\n\nexport default Loader;\n","import { useState } from 'react';\nimport { useQueryDeletion } from './useQueryDeletion';\nimport { toast } from 'react-toastify';\nimport { queriesAsyncThunk } from '../../../store/queries/queries.thunk';\n\nexport const useDeleteQuery = () => {\n const [openModal, setOpenModal] = useState(false);\n\n const toggleModal = () => setOpenModal(!openModal);\n\n const { deleteQuery } = useQueryDeletion();\n\n return {\n openModal,\n toggleModal,\n deleteQuery,\n };\n};\n","import { useCallback, useEffect, useRef } from 'react';\n\ninterface SubscribeToCopyEventPayload {\n onCopy?: (copiedText: string) => void | Promise;\n enable?: boolean;\n}\n\nexport const useSubscribeToCopyEvents = (\n options?: SubscribeToCopyEventPayload\n) => {\n const ref = useRef(null);\n const subscribeToCopiedText = useCallback((_: ClipboardEvent) => {\n const selectedText = window.getSelection().toString();\n options?.onCopy(selectedText);\n }, []);\n useEffect(() => {\n if (ref.current) {\n ref.current?.addEventListener('copy', subscribeToCopiedText);\n }\n\n return () => {\n if (ref.current) {\n ref.current?.removeEventListener('copy', subscribeToCopiedText);\n }\n };\n }, []);\n\n return { ref };\n};\n","import { useState } from 'react';\nimport { useAppDispatch } from '../../../store';\nimport { queryFolderContentThunks } from '../../../store/queryFolderContent/thunk';\nimport { newQueryFolderContentThunks } from '../../../store/newFolderContent/thunk';\nimport { useSherloqToast } from '../../../v2/components/common/SherloqToastProvider';\nimport { useNavigate } from 'react-router-dom';\nimport MixedPanelEvent from '../../../utils/analytics/mixPanel';\n\nexport interface MoveFolderHookParams {\n onCompleteMove?: () => void;\n}\n\nexport const useMoveFolder = ({ onCompleteMove }: MoveFolderHookParams) => {\n const dispatch = useAppDispatch();\n const navigate = useNavigate();\n const [openModal, setOpenModal] = useState(false);\n const [selectedFolder, setSelectedFolder] = useState();\n const toast = useSherloqToast();\n\n const toggleModal = () => {\n if (openModal) {\n MixedPanelEvent.extensionMoveFolderModalClosed({});\n }\n setOpenModal(!openModal);\n };\n const onSelectFolder = (uid: string) => {\n setSelectedFolder(uid);\n MixedPanelEvent.extensionMoveFolderChooseFolderClicked({ folder_uid: uid });\n };\n\n const moveFolder = async (\n selectedFolder: string,\n folderId: string,\n currentFolderId: string,\n folderName: string\n ) => {\n toast('Moving folder...', 'info');\n if (!selectedFolder) {\n toast('Please select a folder', 'error');\n return;\n }\n\n MixedPanelEvent.extensionMoveFolderClicked({ folder_uid: folderId });\n\n await dispatch(\n queryFolderContentThunks.moveFolder({\n folder_id: folderId,\n new_folder_id: selectedFolder,\n folder_name: folderName\n })\n ).then((res) => {\n if (res.meta.requestStatus === 'fulfilled') {\n dispatch(newQueryFolderContentThunks.index({ uid: currentFolderId }));\n dispatch(newQueryFolderContentThunks.index({ uid: selectedFolder }));\n onCompleteMove?.();\n if (openModal) {\n toggleModal();\n }\n \n toast('Folder moved successfully.', 'success')\n } else {\n console.error('error', res)\n }\n }).catch((error) => {\n toast(error.response.data.message, 'error')\n });\n setOpenModal(false);\n };\n\n return {\n openModal,\n toggleModal,\n moveFolder,\n selectedFolder,\n onSelectFolder\n };\n}; ","import { useState } from 'react';\nimport { useAppDispatch } from '../../../store';\nimport { queryFolderContentThunks } from '../../../store/queryFolderContent/thunk';\nimport { newQueryFolderContentThunks } from '../../../store/newFolderContent/thunk';\nimport { useSherloqToast } from '../../../v2/components/common/SherloqToastProvider';\nimport { removeQuery } from '../../../store/navigation';\nimport { useNavigate } from 'react-router-dom';\nexport interface MoveQueryHookParams {\n onCompleteMove?: () => void;\n}\nexport const useMoveQuery = ({ onCompleteMove }: MoveQueryHookParams) => {\n const dispatch = useAppDispatch();\n const navigate = useNavigate();\n const [openModal, setOpenModal] = useState(false);\n const [selectedFolder, setSelectedFolder] = useState();\n const toast = useSherloqToast();\n const toggleModal = () => setOpenModal(!openModal);\n const onSelectFolder = (uid: string) => setSelectedFolder(uid);\n const moveQuery = async (\n selectedFolder: string,\n queryId: string,\n currentFolderId: string,\n queryName: string\n ) => {\n toast('Moving query...', 'info');\n if (!selectedFolder) {\n toast('Please select a folder', 'error');\n return;\n }\n await dispatch(\n queryFolderContentThunks.move({\n query_id: queryId,\n new_folder_id: selectedFolder,\n parentId: queryName //TODO AE this will break, the line was \"query_name: queryName,\" which should also break\n })\n ).then((res) => {\n if (res.meta.requestStatus === 'fulfilled') {\n dispatch(newQueryFolderContentThunks.index({ uid: currentFolderId }));\n dispatch(newQueryFolderContentThunks.index({ uid: selectedFolder }));\n dispatch(removeQuery({ queryUid: queryId }));\n onCompleteMove?.();\n if (openModal) {\n toggleModal();\n }\n \n toast('Query moved successfully.' , 'success', [{\n text: 'Go to folder',\n onClick: () => navigate(`/repository/${selectedFolder}`)\n }])\n } else {\n console.error('error', res)\n }\n }).catch((error) => {\n toast(error.response.data.message, 'error')\n });\n setOpenModal(false);\n };\n return {\n openModal,\n toggleModal,\n moveQuery,\n selectedFolder,\n onSelectFolder\n };\n};\n","import { toast } from 'react-toastify';\nimport { useAppDispatch } from '../../../store';\nimport { newQueryFolderContentThunks } from '../../../store/newFolderContent/thunk';\nimport { queriesAsyncThunk } from '../../../store/queries/queries.thunk';\n\nexport const useQueryDeletion = () => {\n const dispatch = useAppDispatch();\n\n const deleteQuery = async (id: string, parentId: string) => {\n toast('Deleting query', { isLoading: true });\n await dispatch(queriesAsyncThunk.remove(id!));\n dispatch(newQueryFolderContentThunks.index({ uid: parentId }));\n setTimeout(() => {\n toast.dismiss();\n }, 1000);\n };\n\n return {\n deleteQuery\n };\n};\n","import { unionBy } from 'lodash';\nimport { useFolderSummarySelector } from '../../store/folderSummary/selectors';\nimport { useSharedFoldersSelector } from '../../store/sharedFolders/selectors';\n\n/**\n *\n * @constructs A folder tree with shared and private folders\n */\nexport const useFolderTree = () => {\n const privateFolders = useFolderSummarySelector();\n const sharedFolders = useSharedFoldersSelector();\n const folders = unionBy(privateFolders.list, sharedFolders.list, 'uid');\n\n return folders;\n};\n","export default class Platforms {\n private constructor() {}\n public static get platform() {\n const location = window.location;\n if (location.href.includes('athena')) {\n return 'athena';\n }\n if (location.href.includes('bigquery')) {\n return 'bigquery';\n }\n if (location.href.includes('snowflake')) {\n return 'snowflake';\n }\n if (location.hostname.includes('sherloqdata')) {\n return 'sherloq';\n }\n if (location.hostname.includes('app.mode')) {\n return 'modeAnalytics';\n }\n if (location.hostname.includes('github')) {\n return 'github';\n }\n if (location.hostname.includes('notion.so')) {\n return 'notion';\n }\n if (location.hostname.includes('cloud.databricks')) {\n return 'databricks';\n }\n if (location.hostname.includes('superset')) {\n return 'superset';\n }\n if (location.hostname.includes('looker.com')) {\n return 'looker';\n }\n if (location.hostname.includes('tableau')) {\n return 'tableau';\n }\n if (location.hostname.includes('atlassian.com')) {\n return 'atlassian';\n }\n if (location.hostname.includes('looker.com')) {\n return 'looker';\n }\n if (location.hostname.includes('kaggle')) {\n return 'kaggle';\n }\n if (location.hostname.includes('bizlooker')) {\n return 'bizlooker';\n }\n if (location.href.includes('localhost:8888')) {\n return 'jupyter';\n }\n\n return 'Manual';\n }\n}\n\nexport const OPERATED_PLATFORMS = [\n 'athena',\n 'bigquery',\n 'snowflake',\n 'sherloq',\n 'modeAnalytics',\n 'github',\n 'notion',\n 'databricks',\n 'superset',\n 'looker',\n 'tableau',\n 'atlassian',\n 'kaggle',\n 'bizlooker',\n 'jupyter',\n];\n","import axios, { Axios, AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';\nimport Platforms from '../../../utils/platform';\nimport { logout } from '../browser';\n\nexport interface ApiClientConfig extends AxiosRequestConfig {\n isProtected?: boolean;\n}\n\nconst onFulfilInterceptor = (value: AxiosResponse) => {\n return value;\n};\nconst onErrorInterceptor = (error: AxiosError) => {\n if (error.response) {\n // const { status } = error.response;\n // if (status === 401) {\n // logout();\n // }\n throw error;\n }\n};\nexport default class ApiClient {\n /* A private static variable that is being used to store the instance of the axios client. */\n private static _instance: Axios;\n /**\n * > If the token is not set, then set it to the value of the `getAuthToken` function\n * @returns The token is being returned.\n */\n private static _getToken() {\n return localStorage.getItem('sherloq-auth-token');\n }\n /**\n * It creates an instance of axios with the baseURL set to the API_URL environment variable.\n */\n private static _initialize() {\n this._instance = axios.create({\n baseURL: process.env.REACT_APP_API_URL,\n });\n // this._instance.interceptors.response.use(responseFn, responseErrorFn);\n // this._instance.interceptors.request.use(requestFn);\n }\n\n /**\n * If the instance is not yet initialized, initialize it\n * @returns The instance of the class.\n */\n public static get instance() {\n if (!this._instance) {\n this._initialize();\n }\n this._instance.interceptors.response.use(onFulfilInterceptor, onErrorInterceptor);\n return this._instance;\n }\n\n /**\n * It takes in a config object, and returns a new config object\n * @param [config] - This is the configuration object that you pass to the API client.\n * @returns The config object is being returned.\n */\n private static async getConfig(config?: ApiClientConfig) {\n let newConfig: ApiClientConfig = { headers: {} };\n\n if (config) {\n const { isProtected } = config;\n\n newConfig = config;\n if (isProtected && this._getToken !== null) {\n const token = await this._getToken();\n\n const Authorization = `bearer ${token}`;\n newConfig = { ...config };\n newConfig.headers = {\n Authorization,\n 'x-request-origin': Platforms.platform,\n };\n }\n }\n newConfig = { ...newConfig };\n delete newConfig.isProtected;\n return newConfig as AxiosRequestConfig;\n }\n\n /**\n * A generic function that returns a promise of type T.\n * @param {string} url - The url to make the request to.\n * @param {ApiClientConfig} [config] - This is the configuration object that will be passed to the\n * axios instance.\n * @returns The return type is a Promise.\n */\n public static async get(url: string, config?: ApiClientConfig) {\n const newConfig = await this.getConfig(config);\n return this.instance.get(url, newConfig);\n }\n\n /**\n * A generic function that makes a POST request to the given url with the given body and config.\n * @param {string} url - The url to make the request to.\n * @param {D} body - The data you want to send to the server.\n * @param {ApiClientConfig} config - ApiClientConfig - This is the configuration object that we will\n * use to configure the request.\n * @returns A promise that resolves to an AxiosResponse\n */\n public static async post(url: string, body: D, config?: ApiClientConfig, abortSignal?: AbortSignal) {\n const newConfig = { ...await this.getConfig(config), signal: abortSignal};\n return this.instance.post, D>(url, body, newConfig);\n }\n /**\n * A generic function that takes a url, body, and config. It then returns an axios put request.\n * @param {string} url - The url to make the request to.\n * @param {D} body - The data to be sent to the server.\n * @param {ApiClientConfig} config - ApiClientConfig - This is the configuration object that we will\n * use to configure the request.\n * @returns A promise that resolves to the response from the server.\n */\n public static async put(url: string, body: D, config?: ApiClientConfig) {\n const newConfig = await this.getConfig(config);\n return this.instance.put, D>(url, body, newConfig);\n }\n /**\n * \"This function returns a promise that resolves to a response object of type T, and it takes a URL, a\n * body, and a config object as parameters.\"\n *\n * The first parameter is a URL string. The second parameter is a body object of type D. The third\n * parameter is a config object of type ApiClientConfig\n * @param {string} url - The url to make the request to.\n * @param {D} body - The data to be sent to the server.\n * @param {ApiClientConfig} config - ApiClientConfig - This is the configuration object that you can\n * pass to the method.\n * @returns A promise that resolves to an AxiosResponse\n */\n public static async path(url: string, body: D, config?: ApiClientConfig) {\n const newConfig = await this.getConfig(config);\n return this.instance.patch, D>(url, body, newConfig);\n }\n /**\n * \"This function returns a promise that resolves to the response body of a DELETE request to the given\n * URL.\"\n *\n * The first line of the function is a TypeScript type annotation. It's not required, but it's a good\n * idea to include it. It tells TypeScript that the function returns a promise that resolves to a value\n * of type T\n * @param {string} url - The url to make the request to.\n * @param {ApiClientConfig} [config] - This is an optional parameter that allows you to override the\n * default configuration.\n * @returns A Promise\n */\n public static async delete(url: string, config?: ApiClientConfig) {\n const newConfig = await this.getConfig(config);\n return this.instance.delete(url, newConfig);\n }\n\n public static getErrorMessageFromError(error: AxiosError | Error) {\n if (error instanceof AxiosError) {\n return error.response?.data?.data || error.response?.data;\n }\n return error.message;\n }\n}\n","const copyTextToClipboard = (data?: string) => {\n window.navigator.clipboard.writeText(data!);\n};\nexport const logout = () => {\n localStorage.clear();\n location.reload();\n};\nexport const BrowserUtils = Object.freeze({\n copyTextToClipboard,\n});\n\nexport function redirectToNewPage(route: string) {\n const a = document.createElement('a');\n a.setAttribute('href', route);\n a.setAttribute('target', '_blank');\n a.setAttribute('rel', 'noreferrer noopener');\n a.click();\n}\n","import { toast } from 'react-toastify';\nimport { BrowserUtils } from '../browser';\n\ntype DefaultClipboardMenuAction = (\n clipboardText: string,\n message?: string,\n errorMessage?: string,\n) => void;\n\nconst copy: DefaultClipboardMenuAction = async (clipboardText, message, errorMessage) => {\n try {\n await BrowserUtils.copyTextToClipboard(clipboardText);\n toast(message ?? 'Successfully copied to clipboard', { type: 'success' });\n } catch (error) {\n toast(errorMessage ?? 'Failed to copy to Clipboard', { type: 'error' });\n }\n};\n\nconst share: DefaultClipboardMenuAction = (clipboardText, message, errorMessage) => {\n copy(\n clipboardText,\n message ?? 'Shared link successfully copied to clipboard',\n errorMessage ?? 'Failed to copy shared linl to clipbaord',\n );\n};\n\nexport const MenuClipboardActions = Object.freeze({\n share,\n copy,\n});\n","export const RenderIf = (condition: boolean, Component: JSX.Element | JSX.Element[]) => {\n if (condition) return Component;\n return null;\n};\n\nexport const RenderCondition = (condition: boolean, Truthy, Falsy: any) => {\n if (condition) return Truthy;\n return Falsy;\n};\n\nexport const returnIfWithNull = (condition: boolean, val: any) => {\n if (condition) return val;\n return null;\n};\n","import React, { Fragment, ReactNode } from 'react';\n\ninterface Props {\n condition: boolean;\n children: ReactNode | ReactNode[];\n}\nexport const ConditionComponent = ({ children, condition }: Props) => {\n if (!condition) return null;\n return {children} ;\n};\n","export default class WebAccessibleEvents {\n public static GET_SELECTED_TEXT = 'get_selected_text';\n public static DISPATCH_SELECTED_TEXT = 'dispatched_selected_text';\n public static GET_FULL_TEXT = 'get_full_text';\n public static DISPATCH_FULL_TEXT = 'dispatch_full_text';\n public static GET_FULL_AND_SELECTED_TEXT = 'get_both_full_and_selected_text';\n public static DISPATCH_FULL_AND_SELECTED_TEXT = 'dispatch_both_full_and_selected_text';\n public static CREATE_NEW_QUERY = 'create_new_query';\n public static DISPATCH_CREATE_NEW_QUERY = 'dispatch_create_new_query';\n public static CREATE_TAB_LISTENER = 'create_tab_listener';\n public static DISPATCH_CREATE_TAB_LISTENER = 'dispatch_create_tab_listener';\n public static GET_FULL_AND_SELECTED_SAVE_TEXT = 'get_full_and_selected_save_text';\n public static DISPATCH_FULL_AND_SELECTED_SAVE_TEXT = 'dispatch_full_and_selected_save_text';\n public static DISPATCH_TOGGLE_FRAME_WITH_REDIRECT = 'dispatch_toggle_frame_with_redirect';\n public static DISPATCH_NAVIGATE_TO = 'dispatch_navigate_to';\n public static GET_SEARCH_QUERY_TEXT = 'get_search_query_text';\n public static DISPATCH_SEARCH_QUERY_TEXT = 'dispatch_search_query_text';\n}\n","import WebAccessibleEvents from '../../../utils/events';\n\nconst dispatchToggleFrameWithRedirect = (redirectUrl: string) => {\n document.dispatchEvent(\n new CustomEvent(WebAccessibleEvents.DISPATCH_TOGGLE_FRAME_WITH_REDIRECT, {\n detail: {\n redirectUrl,\n },\n }),\n );\n};\n\nexport const CustomEventDispatchManager = Object.freeze({\n dispatchToggleFrameWithRedirect,\n});\n","import WebAccessibleEvents from '../../../utils/events';\n\nconst toggleFrameWithRedirectListener = (cb: (redirectUrl: string) => void) => {\n document.addEventListener(\n WebAccessibleEvents.DISPATCH_TOGGLE_FRAME_WITH_REDIRECT,\n (args: any) => {\n const { redirectUrl } = args.detail;\n cb(redirectUrl);\n },\n );\n};\n\nexport const CustomEventsListenerManager = Object.freeze({\n toggleFrameWithRedirectListener,\n});\n","import ApiClient from '../../../lib/utils/apiClient';\n\nexport const getQueryVersions = async (uid: string) => {\n return ApiClient.post(\n 'query-versions/versions',\n { query_uid: uid },\n {\n isProtected: true,\n },\n );\n};\n\nexport const updateQueryVersion = async (query: any) => {\n return ApiClient.post('query-versions/update', query, {\n isProtected: true,\n });\n};\n\nexport const updateComment = async (req: any) => {\n return ApiClient.post('queries/update/comment', req, {\n isProtected: true,\n });\n};\n\nexport const updateCommentVersion = async (req: any) => {\n return ApiClient.post('query-versions/update/comment', req, {\n isProtected: true,\n });\n};\n\nexport const updateQueryVersionName = async (req: { id: number; name: string }) => {\n return ApiClient.path(\n `query-versions/${req.id}`,\n { name: req.name },\n {\n isProtected: true,\n },\n );\n};\n\nexport const optimizeQueryVersion = async (query: any) => {\n return ApiClient.post(\n 'ai/optimize',\n { query },\n {\n isProtected: true,\n },\n );\n};\n\nexport const documentQueryVersion = async (query: any) => {\n return ApiClient.post(\n 'ai/document',\n { query },\n {\n isProtected: true,\n },\n );\n};\n\nexport const getAIQueryName = async (query: any) => {\n return ApiClient.post(\n 'ai/title',\n { query },\n {\n isProtected: true,\n },\n );\n};\n\nexport const getAIQueryDescription = async (query: any) => {\n return ApiClient.post(\n 'ai/description',\n { query },\n {\n isProtected: true,\n },\n );\n};\n\nexport const getSavedQuery = async (uid: string) => {\n return ApiClient.get(`queries/${uid}`, {\n isProtected: true,\n });\n};\nexport const getShortcuts = async () => {\n return ApiClient.get(`shortcuts/`, {\n isProtected: true,\n });\n};\n\nexport const getShortcut = async (query_uid: string) => {\n return ApiClient.get(`shortcuts/${query_uid}`, {\n isProtected: true,\n });\n};\nexport const addShortcut = async (query_uid: string) => {\n return ApiClient.post('shortcuts/', { query_uid }, { isProtected: true });\n};\n\nexport const deleteShortcut = async (query_uid: string) => {\n return ApiClient.delete(`shortcuts/${query_uid}`, { isProtected: true });\n};\n\n","import React from 'react';\nimport { Stack, StackProps, Typography } from '@mui/material';\nimport { PlatformIcon } from '../../../components/Icons/PlatformIcon';\nimport { UTCtoLocal } from '../helpers/helpers';\nimport { editorFooterContainer } from '../styles';\nimport { RenderIf } from '../../../lib/utils';\n\ninterface EditorFooterData {\n origin: string;\n username: string;\n createdAt: string;\n}\n\ninterface EditorFooterProps extends StackProps {\n data: EditorFooterData;\n}\n\nexport const EditorFooter = ({\n data,\n style = {},\n ...props\n}: EditorFooterProps) => {\n const dataExists = Boolean(data.createdAt && data.username);\n return (\n \n \n \n Created by\n \n {RenderIf(\n dataExists,\n \n {data.username} {' '}\n {UTCtoLocal(data?.createdAt || '')}\n \n )}\n \n );\n};\n","import React from 'react';\nimport { styled, Input, InputProps } from '@mui/material';\n\nexport const QueryDetailInput = styled((props: InputProps) => (\n \n))(({ theme }) => {\n const { focused, border, background } = (theme.palette as any).input;\n return {\n padding: 0,\n '.MuiInput-input': {\n borderWidth: '1px',\n borderStyle: 'solid',\n borderColor: border,\n fontSize: '12px',\n backgroundColor: background,\n borderRadius: '5px',\n padding: '0 4px',\n ':focus': {\n borderColor: focused\n }\n }\n };\n});\n","import { useTypedSelector } from '..';\n\nexport const useNewFolderContentState = (uid: string) =>\n useTypedSelector(state => state.sherloqNewFolderContent.data[uid]);\n\nexport const useNewFolderSingleFolderContentState = (\n uid: string,\n parentId: string\n) =>\n useTypedSelector(state =>\n state.sherloqNewFolderContent.data[parentId]?.list.find(\n item => item.query?.uid === uid || item.child?.uid\n )\n );\n","import moment from 'moment';\nimport { useSingleSearchQuerySelector } from '../../../store/newQueries/selectors';\nimport { useSingleSharedFoldersSelector } from '../../../store/sharedFolders/selectors';\nimport { useSingleSearchQueryHistorySelector } from '../../../store/newQueries/selectors';\nimport { useSingleQueryHistoryNewSelector } from '../../../store/queryHistoryNew/selectors';\nimport { useNewFolderSingleFolderContentState } from '../../../store/newFolderContent/selectors';\n\nexport const fetchContent = (\n selectorType: 'saved' | 'history' | 'search' | 'my-history' | 'public',\n id: string,\n parentId: string,\n) => {\n if (selectorType === 'saved') {\n let data = useNewFolderSingleFolderContentState(id!, parentId);\n return {\n name: data?.query?.name || '',\n parent_name: data?.parent?.name || '',\n description: data?.query?.description || '',\n query: data?.query?.query || '',\n uid: data?.query?.uid || '',\n created_at: data?.query?.created_at || '',\n origin: data?.query?.origin || '',\n user_uid: '',\n user_first_name: '',\n user_last_name: '',\n user_email: '',\n };\n }\n if (selectorType === 'history') {\n let data = useSingleSearchQueryHistorySelector(id!);\n return {\n name: '',\n parent_name: '' || '',\n description: '',\n query: data?.query || '',\n uid: data?.uid || '',\n created_at: data?.created_at || '',\n origin: data?.origin || '',\n user_uid: data?.user_uid || '',\n user_first_name: data?.first_name || '',\n user_last_name: data?.last_name || '',\n user_email: '',\n };\n }\n\n if (selectorType === 'public') {\n // TODO: Add public folders selector\n let data = useSingleSharedFoldersSelector(id!);\n return {\n name: '',\n parent_name: '' || '',\n description: '',\n // query: data?.query || '',\n uid: data?.uid || '',\n created_at: data?.created_at || '',\n // origin: data?.origin || '',\n user_uid: data?.user_id || '',\n user_first_name: data?.user_first_name || '',\n user_last_name: data?.user_last_name || '',\n user_email: '',\n is_shared: data?.is_shared,\n };\n }\n if (selectorType === 'my-history') {\n let data = useSingleQueryHistoryNewSelector(id!);\n return {\n name: '',\n parent_name: '' || '',\n description: '',\n query: data?.query || '',\n uid: data?.uid || '',\n created_at: data?.created_at || '',\n origin: data?.origin || '',\n user_uid: data?.user?.uid || '',\n user_first_name: data?.user?.firstname || '',\n user_last_name: data?.user?.lastname || '',\n user_email: '',\n };\n }\n if (selectorType === 'search') {\n let data = useSingleSearchQuerySelector(id!);\n return {\n name: data?.query_name || '',\n parent_name: data?.folder_name || '',\n description: data?.query_description || '',\n query: data?.query_query || '',\n uid: data?.query_uid || '',\n created_at: data?.created_at || '',\n origin: data?.origin || '',\n user_uid: data?.user_id || '',\n user_first_name: data?.first_name || '',\n user_last_name: data?.last_name || '',\n user_email: '',\n };\n }\n};\nexport const localTime = (date: any, second: boolean = false) => {\n let d;\n if (typeof date == 'number') {\n d = new Date(date * 1000).getTime();\n } else {\n d = new Date(date).getTime();\n }\n let dateTemp = new Date(d);\n let formattedDate = dateTemp.toLocaleDateString('en-GB', {\n year: '2-digit',\n month: '2-digit',\n day: '2-digit',\n });\n let formattedTime = '';\n if (second) {\n formattedTime = dateTemp.toLocaleTimeString('en-US', {\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n });\n } else {\n formattedTime = dateTemp.toLocaleTimeString('en-US', {\n hour: '2-digit',\n minute: '2-digit',\n });\n }\n return `${formattedDate} ${formattedTime}`;\n};\n\nexport const UTCtoLocal = (utcTimestamp: string, withOffset: boolean = true) => {\n const date = new Date(utcTimestamp);\n const offset = date.getTimezoneOffset();\n const year = date.getFullYear().toString().substr(-2);\n const month = ('0' + (date.getMonth() + 1)).slice(-2);\n const day = ('0' + date.getDate()).slice(-2);\n const hour = ('0' + (date.getHours() - (withOffset ? (offset / 60) : 0))).slice(-2);\n const minute = ('0' + date.getMinutes()).slice(-2);\n const period = hour >= '12' ? 'PM' : 'AM';\n const formattedDate = `${day}/${month}/${year} ${parseInt(hour) % 12}:${minute} ${period}`;\n return formattedDate;\n};\n\nexport const shortenString = (value: string, len: number) => {\n if (value && value.length > len) {\n return value.substring(0, len).concat('...');\n }\n return value;\n};\n","import { useTypedSelector } from '..';\nimport { QueryHistorySearchResult, QuerySearchResponse } from './type';\n\nexport const useNewQueriesSelector = () =>\n useTypedSelector(({ sherloqNewQueries }) => sherloqNewQueries);\n\nexport const useSingleSearchQuerySelector = (uid: string) =>\n useTypedSelector(({ sherloqNewQueries }) =>\n (sherloqNewQueries.list as QuerySearchResponse[]).find((item) => item?.query_uid === uid),\n );\n\nexport const useSingleSearchQueryHistorySelector = (uid: string) =>\n useTypedSelector(({ sherloqNewQueries }) =>\n (sherloqNewQueries.list as QueryHistorySearchResult[]).find((item) => item?.uid === uid),\n );\n","import { sx } from '../../styles/MUI/styles';\nimport { isDatagrip } from '../../utils/checkOrigin';\n\nconst selectFolderButtonStyles = {\n border: '1px solid #d9d9d9',\n padding: 0,\n backgroundColor: '#fff',\n paddingLeft: '2px'\n};\n\nconst makeSelectFolderStyles = darkMode => {\n const styles = {\n ...(darkMode ? sx.sx1dark : sx.sx1),\n fontSize: '12px',\n maxWidth: '120px'\n };\n return styles;\n};\n\nconst editorFolderDetailsContainer = {\n marginBottom: '0px',\n display: 'flex',\n alignItems: 'center'\n};\n\nconst editorFooterContainer = {\n marginTop: 'auto',\n paddingBottom: '24px'\n};\n\nconst editorAdvancedFeaturesContainer = {\n height: '20px'\n};\n\nconst editorQueryDetailContainerStyles = {\n padding: '24px'\n};\n\nconst editorLayoutContainerStyles = {\n paddingLeft: 0,\n paddingRight: 0,\n paddingTop: 0\n};\n\nexport {\n selectFolderButtonStyles,\n makeSelectFolderStyles,\n editorFolderDetailsContainer,\n editorFooterContainer,\n editorAdvancedFeaturesContainer,\n editorQueryDetailContainerStyles,\n editorLayoutContainerStyles\n};\n","export interface BackgroundMessage {\n type: MessageConstants;\n data?: Record;\n}\n\nexport default class MessageConstants {\n public static LOGIN = 'login';\n public static COOKIE = 'cookie';\n public static LOGOUT_FROM_WEB = 'logout_from_web';\n public static GET_REQUEST_BODY = 'get_request_body';\n public static GET_BIGQUERY_REQUEST_BODY = 'get_big_query_request)body';\n public static GET_SNOWFLAKE_REQUEST_BODY = 'get_snowflake_request)body';\n public static BROWSER_ACTION_CLICKED_MESSAGE = 'clicked_browser_action';\n}\n\nexport interface CookieRequestOptions {\n name: string;\n url: string;\n}\n\nexport const bgColors = ['#EDDD50', '#60D77A', '#FFA449', '#7209B7', '#F72585'];\n","import { useState } from 'react';\nimport { FilePond, registerPlugin } from 'react-filepond';\nimport FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type';\nimport 'filepond/dist/filepond.min.css';\nimport React from 'react';\nimport ApiClient from '../../../lib/utils/apiClient';\nimport './index.scss';\n\nregisterPlugin(FilePondPluginFileValidateType);\n\nconst FileUpload = ({ onUploadDone }: any) => {\n const allowedFileUploadTypes = [\n '.csv',\n '.xlsx',\n '.txt',\n '.sql',\n '.doc',\n '.zip',\n ];\n\n const [files, setFiles] = useState([]);\n\n const handleProcessing = (\n fieldName: string,\n file: any,\n metadata: any,\n load: any,\n error: any,\n progress: any,\n abort: any\n ) => {\n const data = new FormData();\n data.append('file', file, file.name);\n\n ApiClient.post('queries/import-queries', data, {\n isProtected: true,\n onUploadProgress: progressEvent => {\n progress(\n progressEvent.lengthComputable,\n progressEvent.loaded,\n progressEvent.total\n );\n }\n })\n .then(response => {\n load(file.name);\n onUploadDone();\n })\n .catch(err => {\n error(err.message);\n abort();\n });\n };\n\n return (\n <>\n {\n return new Promise((resolve, reject) => {\n if (!source.name) {\n resolve(null);\n return;\n }\n\n const fileExtension = source.name.split('.').pop();\n /**\n * Since we're using a file extension validation for the file types in the FilePond library, it expects one of the allowed\n * extensions, including the dot.\n */\n resolve(`.${fileExtension}`);\n })\n }}\n server={{ process: handleProcessing }}\n onupdatefiles={fileItems => {\n setFiles(fileItems.map(fileItem => fileItem.file));\n }}\n />\n >\n );\n};\n\nexport default FileUpload;\n","import './index.scss';\nimport React, { useState } from 'react';\nimport { sx } from '../../styles/MUI/styles';\nimport { Box, Button, Typography, TextField } from '@mui/material';\nimport FileUpload from '../../utils/analytics/fileUploader';\nimport { toast } from 'react-toastify';\nimport { ModalRoot } from './root';\nimport { RenderIf } from '../../lib/utils';\nimport { ModalTypography } from './components/label';\nimport { Input } from '../Input';\nimport MixedPanelEvent from '../../utils/analytics/mixPanel';\n\ninterface ImportQueriesModalProps {\n importFrom: string;\n show: boolean;\n setText: any;\n setClose: () => void;\n handlerSubmit: () => void;\n showPostQueriesModal: () => void;\n}\n\nconst importFromGitSubTitle =\n 'Importing queries from GIT will be available soon, we will reach out once it will be ready... stay tuned!';\nconst importFromOtherToolSubTitle =\n 'From what tool are you looking to extract your queries?';\n\nconst ImportQueriesModal = ({\n importFrom,\n show,\n setClose,\n handlerSubmit,\n setText,\n showPostQueriesModal\n}: ImportQueriesModalProps) => {\n const importQueriesTemplateUrl = 'https://docs.google.com/spreadsheets/d/10abqOrdg35JW4AHI6uOzUClFLFWBc6xKuxNS7krZB_g';\n const importQueriesFromIdeUrl = 'https://docs.sherloqdata.io/importqueries';\n const [showSubmitButton, setShowSubmitButton] = useState(false);\n const title = importFrom === 'github' ? 'Coming soon...' : 'Import queries';\n const subTitle =\n importFrom === 'github'\n ? importFromGitSubTitle\n : importFromOtherToolSubTitle;\n const submitButtonLabel = importFrom === 'github' ? 'Got it' : 'Send';\n\n const handleUploadDone = () => {\n setShowSubmitButton(true);\n };\n const handleSubmitButtonClick = () => {\n showPostQueriesModal();\n setShowSubmitButton(false);\n };\n const handleSendButtonClick = () => {\n if (importFrom === 'github') {\n handlerSubmit();\n } else {\n const element = document.querySelector('#sherloq-text') as any;\n if (element) {\n if (!element.value) {\n toast('Please fill in your tool name.', { type: 'error' });\n } else {\n handlerSubmit();\n }\n }\n }\n };\n\n const handleOnClose = () => {\n setShowSubmitButton(false);\n setClose();\n };\n\n return (\n \n {title} \n \n {RenderIf(\n importFrom !== 'file',\n {subTitle} \n )}\n {RenderIf(\n importFrom === 'file',\n \n \n Supported file formats: CSV, XLSX, SQL, DOC , ZIP and TXT. For best results, we recommend using our\n \n { MixedPanelEvent.extensionImportTemplateClicked({}); open(importQueriesTemplateUrl)}}\n display={'inline'}\n className=\"sub-title link-text\"\n >\n {' '}\n CSV / XLSX template\n \n \n .\n \n \n )}\n \n \n {importFrom !== 'github' ? (\n importFrom === 'file' ? (\n \n ) : (\n setText(e)}\n multiline={true}\n rows={1}\n minRows={2}\n />\n )\n ) : (\n ''\n )}\n \n {RenderIf(\n importFrom !== 'file',\n \n {submitButtonLabel}\n \n )}\n {RenderIf(\n showSubmitButton,\n \n Submit\n \n )}\n \n \n ⓘ \n \n Learn how to import queries from your IDE\n \n { MixedPanelEvent.extensionImportLearnMoreClicked({}); open(importQueriesFromIdeUrl)}}\n display={'inline'}\n className=\"sub-title link-text\"\n >\n {' '}\n here\n \n \n .\n \n \n \n );\n};\n\nexport default ImportQueriesModal;\n","import './index.scss';\nimport React from 'react';\nimport { Box } from '@mui/material';\nimport { useUserInfoSelector } from '../../store/auth/selectors';\nimport { ModalRoot } from './root';\nimport { Typography } from '../../atoms/Typography';\n\nconst PostImportQueriesModal = ({ show, setClose }: any) => {\n const userInfoSelector = useUserInfoSelector();\n\n return (\n \n Success! \n \n \n Thank you for submitting the request{userInfoSelector?.first_name ? `, ${userInfoSelector.first_name}` : null}.\n \n The import process can take up to 24 hours - we will let you know once it's completed.\n \n \n \n );\n};\n\nexport default PostImportQueriesModal;\n","import React, { useEffect, useState } from 'react'\nimport { styled, Box, Typography, IconButton } from '@mui/material'\nimport UserAvatar from '../common/UserAvatar'\nimport ApiClient from '../../../lib/utils/apiClient'\nimport MembersAutocomplete from '../common/MembersAutocomplete'\nimport { SherloqButton } from '../common/SherloqButton'\nimport RemoveIcon from '../icons/Remove'\nimport { useUserInfoSelector } from '../../../store/auth/selectors'\nimport { CustomTooltip } from '../../../atoms/CustomTooltip'\nimport { createNewUser } from '../../api/admins'\nimport AlertModal from '../Header/Modals/AlertModal'\nimport MixedPanelEvent from '../../../utils/analytics/mixPanel'\n\nconst AdminsContainer = styled(Box)`\n\n`\n\nconst AdminListContainer = styled(Box)`\n padding: 4px 0 10px 0;\n border-bottom: 1px solid #35353B;\n`\n\nconst AssignContainer = styled(Box)`\n padding: 14px 0 0;\n`\n\nconst Card = styled(Box)(({ theme, color }) => `\n padding: 14px;\n background: ${theme.palette.background.paper};\n border-radius: 8px;\n position: relative;\n margin-top: 12px;\n`)\n\nconst CardTitle = styled(Typography)(({ theme, color }) => `\n fontSize: 16px;\n fontWeight: 500;\n color: ${theme.palette.text.primary};\n`)\n\nconst Admins = () => {\n const [admins, setAdmins] = useState([])\n const [isLoading, setIsLoading] = useState(false)\n const [showAddModal, setShowAddModal] = useState(false)\n const [showRemoveModal, setShowRemoveModal] = useState(false)\n const [selectedMembers, setSelectedMembers] = useState([])\n const [adminToRemove, setAdminToRemove] = useState(null)\n const userInfo = useUserInfoSelector()\n\n const setAdmin = async (isAdmin, user = null) => {\n setIsLoading(true)\n if (isAdmin && !user && !selectedMembers[0]?.uid && selectedMembers[0]?.email) {\n await createNewUser({ email: selectedMembers[0]?.email, isAdmin: true })\n } else {\n await ApiClient.put(`/accounts/auth/user/${user?.uid || selectedMembers[0]?.uid}`, { is_admin: isAdmin }, { isProtected: true })\n }\n if (isAdmin) {\n MixedPanelEvent.extensionAdminAssigned({ assignedEmail: user?.email || selectedMembers[0]?.email, existingUser: !!user })\n }\n const newAdmins = isAdmin ? [...admins, user || selectedMembers[0]] : [...admins].filter(admin => admin.email !== (user.email || selectedMembers[0]?.email))\n setAdmins(newAdmins)\n setSelectedMembers([])\n setIsLoading(false)\n }\n useEffect(() => {\n ApiClient.get('/accounts/auth/admins', { isProtected: true }).then((res) => {\n setAdmins(res.data)\n })\n }, [])\n return (\n \n \n Admins \n \n {(admins || []).map((admin) => (\n \n \n \n \n \n {admin?.first_name} {admin?.last_name}\n \n \n {admin.email}{admin.is_pending ? ' (pending)' : ''}\n \n \n \n \n { if (admin.email !== userInfo.email) { setShowRemoveModal(true); setAdminToRemove(admin) }}}>\n \n \n \n \n ))}\n \n \n Assign new admins \n \n admin.email)} placeholder='Email address' setSelectedMembers={setSelectedMembers} selectedMembers={selectedMembers} isMultiple={false} />\n { setShowAddModal(true) }}\n sx={{ width: '200px' }}\n disabled={selectedMembers.length === 0}\n >\n Assign Admin\n \n \n \n \n {showAddModal && setShowAddModal(false)} handleSubmit={async () => { await setAdmin(true); setShowAddModal(false) }} title={`Are you sure you want to add ${selectedMembers[0]?.email} as an admin?`} />}\n {showRemoveModal && setShowRemoveModal(false)} handleSubmit={async () => { await setAdmin(false, adminToRemove); setShowRemoveModal(false); MixedPanelEvent.extensionAdminUnassigned({}) }} title={`Are you sure you want to remove ${adminToRemove?.email} from being an admin?`} />}\n \n )\n}\n\nexport default Admins","import React, { useEffect, useRef, useState } from 'react'\nimport { Box, styled, IconButton, Typography } from '@mui/material'\nimport { ModalRoot } from '../../../components/modals/root'\nimport { ModalTypography } from '../../../components/modals/components/label'\nimport { Input } from '../../../components/Input'\nimport MembersAutocomplete from '../common/MembersAutocomplete'\nimport { useInternalTeamsSelector } from '../../../store/internalTeams/selectors'\nimport { useTeamMembersSelector } from '../../../store/teamMembers/selectors'\nimport { LoadingState } from '../../../utils/types/general'\nimport UserAvatar from '../common/UserAvatar'\nimport { CustomTooltip } from '../../../atoms/CustomTooltip'\nimport RemoveIcon from '../icons/Remove'\nimport { SherloqButton } from '../common/SherloqButton'\nimport { isEqual } from 'lodash';\nimport { createNewTeam, createNewUser, updateInternalTeam } from '../../api/admins'\nimport { internalTeamThunk } from '../../../store/internalTeams/thunk'\nimport { useAppDispatch } from '../../../store'\nimport { useSherloqToast } from '../common/SherloqToastProvider'\nimport Loader from '../../../lib/components/Loader/Loader'\nimport MixedPanelEvent from '../../../utils/analytics/mixPanel'\nimport { teamMemberThunk } from '../../../store/teamMembers/thunk'\n\n\nconst TeamModalContainer = styled(Box)`\n\n`\n\nconst MemberListContainer = styled(Box)`\n border-style: solid;\n border-width: 1px;\n border-radius: 8px;\n margin-top: 12px;\n`\n\nconst LoaderContainer = styled(Box)`\n div {\n margin-top: 0;\n }\n span {\n width: 28px !important;\n height: 28px !important;\n }\n`\n\nconst TeamModal = ({ handleClose, teamId }: { handleClose: () => void, teamId: number }) => {\n const { list: teams, loading: loadingTeams } = useInternalTeamsSelector();\n const { list: teamMembers, loading: loadingMembers } = useTeamMembersSelector()\n const toast = useSherloqToast()\n const dispatch = useAppDispatch()\n const [isLoading, setIsLoading] = useState(false)\n const [name, setName] = useState('')\n const [originName, setOriginName] = useState('')\n const [selectedMembers, setSelectedMembers] = useState([]);\n const [originMembers, setOriginMembers] = useState([]);\n const isSaveDisabled = isEqual(selectedMembers, originMembers) && name === originName;\n\n const createTeam = async () => {\n setIsLoading(true)\n try {\n const createUserPromises = selectedMembers\n .filter(m => !teamMembers.find(tm => tm.email === m.email))\n .map(member =>\n createNewUser({ email: member.email, firstName: '', lastName: '', isAdmin: false })\n );\n // Wait for all createNewUser promises to complete\n await Promise.all(createUserPromises);\n await createNewTeam({ name, user_emails: selectedMembers.map(sm => sm.email) })\n dispatch(internalTeamThunk.index())\n dispatch(teamMemberThunk.index())\n setIsLoading(false)\n toast(`Team ${name} has been updated!`, 'success')\n // Extract emails for comparison\n const originEmails = originMembers.map(member => member.email);\n const selectedEmails = selectedMembers.map(member => member.email);\n\n // Calculate differences\n const membersAddedDiff = selectedEmails.filter(email => !originEmails.includes(email));\n const membersRemovedDiff = originEmails.filter(email => !selectedEmails.includes(email));\n\n MixedPanelEvent.extensionAdminNewTeamAdded({ teamName: name, members: selectedMembers.map(sm => sm.email), membersAddedDiff, membersRemovedDiff })\n handleClose()\n } catch (e) {\n toast(`There has been an issue with creating the team. Please try again.`, 'error')\n setIsLoading(false)\n handleClose()\n }\n\n }\n\n const updateTeam = async () => {\n setIsLoading(true)\n try {\n const createUserPromises = selectedMembers\n .filter(sm => !teamMembers.find(tm => tm.email === sm.email))\n .map(member =>\n createNewUser({ email: member.email, firstName: '', lastName: '', isAdmin: false })\n );\n // Wait for all createNewUser promises to complete\n await Promise.all(createUserPromises);\n await updateInternalTeam(teamId, { name, user_emails: selectedMembers.map(sm => sm.email) })\n dispatch(internalTeamThunk.index())\n setIsLoading(false)\n toast(`Team ${name} has been updated!`, 'success')\n MixedPanelEvent.extensionAdminNewTeamAdded({ teamName: name, members: selectedMembers.map(sm => sm.email) })\n handleClose()\n } catch (e) {\n setIsLoading(false)\n toast(`There has been an issue with the team update. Please try again.`, 'error')\n handleClose()\n }\n\n }\n\n useEffect(() => {\n if (loadingTeams === LoadingState.FULFILLED && loadingMembers === LoadingState.FULFILLED) {\n const team = teams.find(t => t.id === teamId)\n const members = teamMembers.filter(tm => team?.members.map(m => m.user_email).includes(tm.email)) || []\n setSelectedMembers(members)\n setOriginMembers(members)\n setName(team?.name)\n setOriginName(team?.name)\n }\n }, [teamId, loadingMembers])\n\n return (\n \n \n {teamId === -1 ? 'New team' : 'Manage team'} \n textarea': { borderRadius: '8px !important', height: '17px !important', lineHeight: '17px !important', padding: '11px 16px !important' } }}\n minRows={1}\n fullWidth\n value={name}\n onChange={e => setName(e.target.value)}\n placeholder='Enter team name'\n disabled={isLoading}\n />\n \n {selectedMembers?.length > 0 && \n {(selectedMembers || []).map((member) => (\n \n \n \n \n \n {member?.first_name ? `${member?.first_name} ${member?.last_name}` : member.email}{member.is_pending ? ' (pending)' : ''}\n \n \n \n \n { setSelectedMembers(selectedMembers.filter(sm => sm.email !== member.email)) }}>\n \n \n \n \n ))}\n }\n \n \n Cancel\n \n \n \n { teamId === -1 ? createTeam() : updateTeam() }}\n isLoading={isLoading}\n >\n {isLoading ? : 'Save'}\n \n \n \n \n \n \n )\n}\n\nexport default TeamModal","import React, { useCallback, useState, useMemo } from 'react'\nimport { styled, Box, Typography, IconButton } from '@mui/material'\nimport { useInternalTeamsSelector } from '../../../store/internalTeams/selectors'\nimport { SherloqButton } from '../common/SherloqButton'\nimport { useTeamMembersSelector } from '../../../store/teamMembers/selectors'\nimport { useUserInfoSelector } from '../../../store/auth/selectors'\nimport UserAvatar from '../common/UserAvatar'\nimport useSherloqTheme from '../../../styles/useSherloqTheme'\nimport ActionsMenu from '../common/ActionsMenu'\nimport TeamModal from './TeamModal'\nimport { deleteInternalTeam } from '../../api/admins'\nimport AlertModal from '../Header/Modals/AlertModal'\nimport { internalTeamThunk } from '../../../store/internalTeams/thunk'\nimport { useAppDispatch } from '../../../store'\nimport { useSherloqToast } from '../common/SherloqToastProvider'\nimport MixedPanelEvent from '../../../utils/analytics/mixPanel'\n\nconst ManageTeamsContainer = styled(Box)`\n\n`\n\nconst Card = styled(Box)(({ theme, color }) => `\n padding: 14px;\n background: ${theme.palette.background.paper};\n border-radius: 8px;\n position: relative;\n margin-top: 12px;\n`)\n\nconst CardTitle = styled(Typography)(({ theme, color }) => `\n fontSize: 16px;\n fontWeight: 500;\n color: ${theme.palette.text.primary};\n`)\n\nconst CardSubtitle = styled(Typography)(({ theme, color }) => `\n font-size: 12px;\n color: ${theme.palette.text.secondary};\n`)\n\nconst TeamsContainer = styled(Box)`\n border-radius: 8px;\n border-style: solid;\n border-width: 1px;\n margin-top: 12px;\n`\n\nconst TeamContainer = styled(Box)(({ theme }) => `\n width: 100%;\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 8px 0 8px 14px;\n\n &:not(:last-child) {\n border-bottom: 1px solid ${theme.palette.input.border};\n }\n`)\n\nconst TeamTitle = styled(Typography)`\n font-size: 14px;\n`\n\nconst TeamMembers = styled(Typography)`\n font-size: 12px;\n`\n\nconst MemberAvatar = styled(Box)`\n margin-right: -4px;\n height: 18px;\n >div {\n outline-width: 2px;\n outline-style: solid;\n }\n`\n\nconst ManageTeams = () => {\n const { list: teams, loading } = useInternalTeamsSelector();\n const { list: teamMembers } = useTeamMembersSelector()\n const [isLoading, setIsLoading] = useState(false)\n const userInfo = useUserInfoSelector()\n const theme = useSherloqTheme()\n const [teamToManage, setTeamToManage] = useState(0)\n const [teamToDelete, setTeamToDelete] = useState(0)\n const [showRemoveModal, setShowRemoveModal] = useState(false)\n const dispatch = useAppDispatch()\n const toast = useSherloqToast()\n\n const getCompanyName = useCallback((email: string) => {\n const domain = email?.split('@')[1] || ''\n const company = domain.split('.')[0]\n return company.charAt(0).toUpperCase() + company.slice(1)\n }, [])\n\n const everyoneTeam = useMemo(() => {\n if (!userInfo?.email) return null\n const companyName = getCompanyName(userInfo.email)\n return teams.find(team => team.name === `Everyone in ${companyName}`)\n }, [userInfo?.email, teams, getCompanyName])\n\n const handleTeamRemove = async () => {\n const teamName = teams.find(team => team.id === teamToDelete)?.name\n setIsLoading(true);\n try {\n const response = await deleteInternalTeam(teamToDelete);\n if (response instanceof Error) {\n toast('Unable to delete team - please remove it from all shared folders first.', 'error');\n } else {\n await dispatch(internalTeamThunk.index());\n toast('Team has been deleted.', 'success');\n MixedPanelEvent.extensionAdminRemoveTeam({ teamName });\n setShowRemoveModal(false);\n }\n } catch (error) {\n toast('Unable to delete team - please remove it from all shared folders first.', 'error');\n } finally {\n setIsLoading(false);\n }\n }\n\n const teamMembersString = (team) => {\n let string = ''\n let count = 0\n if (team.members.find(tm => tm.user_email === userInfo?.email)) {\n string = 'You, '\n count = 1\n }\n const members = team.members.filter(tm => tm.user_email !== userInfo?.email)\n if (members.length) {\n let member = teamMembers.find(tm => tm.email === members[0]?.user_email)\n string += member?.first_name ? `${member?.first_name} ${member?.last_name}` : `${member?.email}`\n count++;\n }\n if (members.length >= 2) {\n let member = teamMembers.find(tm => tm.email === members[1]?.user_email)\n string += member?.first_name ? `, ${member.first_name} ${member.last_name}` : `, ${member?.email}`\n count++;\n }\n if (members.length >= 3) {\n string += ` +${team.members.length - count}`\n }\n return string\n }\n\n return (\n \n { teamToManage !== 0 && { setTeamToManage(0)}}/> }\n \n Manage Teams \n {teams.length} teams found \n { setTeamToManage(-1); MixedPanelEvent.extensionAdminNewTeamClicked({ }); }} sx={{ width: '120px', position: 'absolute', right: '14px', top: '14px' }}>+ New Team \n {teams.length > 0 && \n {everyoneTeam && (\n \n \n {everyoneTeam.name} \n {teamMembersString(everyoneTeam)} \n \n \n {everyoneTeam.members.map((tm, i) => i < 3 && div': { outlineColor: theme.palette.background.default } }}>\n t.email == tm.user_email)?.first_name}\n lastName={teamMembers.find(t => t.email == tm.user_email)?.last_name}\n email={teamMembers.find(t => t.email == tm.user_email)?.email}\n picture={teamMembers.find(t => t.email == tm.user_email)?.picture} />\n )}\n \n \n )}\n {teams.filter(team => team.id !== everyoneTeam?.id).map(team => (\n \n \n {team.name} \n {teamMembersString(team)} \n \n \n {team.members.map((tm, i) => i < 3 && div': { outlineColor: theme.palette.background.default } }}>\n t.email == tm.user_email)?.first_name}\n lastName={teamMembers.find(t => t.email == tm.user_email)?.last_name}\n email={teamMembers.find(t => t.email == tm.user_email)?.email}\n picture={teamMembers.find(t => t.email == tm.user_email)?.picture} />\n )}\n { setTeamToManage(team.id); MixedPanelEvent.extensionAdminEditTeamIconClicked({})} },\n { text: 'Remove team', onClick: () => { setShowRemoveModal(true); setTeamToDelete(team.id); MixedPanelEvent.extensionAdminRemoveTeamIconClicked({})} },\n ]}\n />\n \n \n ))}\n }\n \n {showRemoveModal && setShowRemoveModal(false)} handleSubmit={handleTeamRemove} title={`Are you sure you want to remove team \"${teams.find(t => t.id == teamToDelete)?.name ?? ''}\"?`} />}\n \n )\n}\n\nexport default ManageTeams","import React from 'react'\nimport {SvgIcon, SvgIconProps} from '@mui/material'\n\nconst InfoIcon: React.FC = (props) => {\n return (\n \n \n \n\n )\n}\n\nexport default InfoIcon","import React, { useEffect, useState } from 'react'\nimport { styled, Box, Typography, IconButton, Chip } from '@mui/material'\nimport { CloseSvg, TeamAvatars } from '../../../components/modals/components/teamMembersSelector'\nimport { useInternalTeamsSelector } from '../../../store/internalTeams/selectors'\nimport { useTeamMembersSelector } from '../../../store/teamMembers/selectors'\nimport SherloqDropdown from '../common/SherloqDropdown'\nimport { internalTeamThunk } from '../../../store/internalTeams/thunk'\nimport { useAppDispatch } from '../../../store'\nimport { updateInternalTeam } from '../../api/admins'\nimport { CustomTooltip } from '../../../atoms/CustomTooltip'\nimport InfoIcon from '../icons/Info'\nimport { LoadingState } from '../../../utils/types/general'\nimport { useSherloqToast } from '../common/SherloqToastProvider'\nimport MixedPanelEvent from '../../../utils/analytics/mixPanel'\nimport ApiClient from '../../../lib/utils/apiClient'\nimport SystemUpdateAltIcon from '@mui/icons-material/SystemUpdateAlt'\nimport AlertModal from '../Header/Modals/AlertModal'\nimport useIsPlaytika from '../../../pages/Watson/hooks/useIsPlaytika'\nimport { dotenv } from '../../../env'\n\nconst TeamQueryHistoryContainer = styled(Box)`\n\n`\n\nconst Card = styled(Box)(({ theme, color }) => `\n padding: 14px;\n background: ${theme.palette.background.paper};\n border-radius: 8px;\n position: relative;\n margin-top: 12px;\n`)\n\nconst CardTitle = styled(Typography)(({ theme, color }) => `\n fontSize: 16px;\n fontWeight: 500;\n color: ${theme.palette.text.primary};\n`)\n\n\nconst TagsContainer = styled(Box)`\n margin-top: 12px;\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n width: 100%;\n`\n\nconst Content = styled(Box)`\n margin-top: 12px;\n`\n\nconst TeamQueryHistory = () => {\n const { list: teams, loading: teamsLoading } = useInternalTeamsSelector();\n const [isLoading, setIsLoading] = useState(false)\n const { list: teamMembers } = useTeamMembersSelector();\n const dispatch = useAppDispatch();\n const [tempList, setTempList] = useState([])\n const toast = useSherloqToast();\n const [isMigrationTriggered, setIsMigrationTriggered] = useState(false)\n const [isSearchMigrationModalOpen, setIsSearchMigrationModalOpen] = useState(false)\n const isPlaytika = useIsPlaytika();\n\n const internalHistoryTeams = teams.filter(t => t.is_internal_history);\n\n const teamOptions = () => {\n return teams.map(team => ({\n label: team.name,\n value: team.id.toString(),\n isSelected: false,\n rightComponent: i < 3).map(internalMember => teamMembers.find(member => internalMember.user_email === member.email))} />\n }))\n }\n\n const onClickTeam = async (teamId: number) => {\n if (internalHistoryTeams.find(t => t.id === teamId)) return;\n setIsLoading(true)\n const team = teams.find(t => t.id == teamId)\n try {\n setTempList([...internalHistoryTeams, team])\n await updateInternalTeam(teamId, { name: team.name, user_emails: team.members.map(m => m.user_email), is_internal_history: true })\n await dispatch(internalTeamThunk.index())\n setIsLoading(false)\n toast('Team Query History had been updated.', 'success')\n MixedPanelEvent.extensionAdminTeamHistoryAdd({ teamName: team.name })\n } catch(e) {\n console.error(e)\n setIsLoading(false)\n }\n\n }\n\n const onRemoveTeam = async (teamId: number) => {\n const team = teams.find(t => t.id == teamId)\n setIsLoading(true)\n try {\n setTempList(internalHistoryTeams.filter(t => t.id !== team.id))\n await updateInternalTeam(teamId, { name: team.name, user_emails: team.members.map(m => m.user_email), is_internal_history: false })\n dispatch(internalTeamThunk.index())\n toast('Team Query History had been updated.', 'success')\n setIsLoading(false)\n MixedPanelEvent.extensionAdminTeamHistoryRemove({ teamName: team.name })\n } catch(e) {\n console.error(e)\n setIsLoading(false)\n }\n }\n\n /**\n * This is a helper button to allow admins to trigger the search migration manually.\n * Once we resolve the issue of doing this automatically with our Helm chart setup, we can remove this button.\n * This functionality is only relevant for on-premise deployments.\n */\n const runOpenSearchV3SearchMigration = async () => {\n setIsSearchMigrationModalOpen(false)\n setIsLoading(true)\n\n try {\n await ApiClient.post('/queries/search/migrate/v3', { target_domain: 'playtika.com' }, { isProtected: true })\n toast('Search migration process started, it will take a few minutes to complete.', 'success')\n setIsMigrationTriggered(true)\n } catch (e) {\n console.error(e)\n toast('Could not trigger search migration, please try again later.', 'error')\n } finally {\n setIsLoading(false)\n }\n }\n\n useEffect(() => {\n setTempList(internalHistoryTeams)\n }, [internalHistoryTeams.length])\n\n return (\n \n \n \n Team Query History\n \n {dotenv.isOnPremise && (\n \n setIsSearchMigrationModalOpen(true)} \n disabled={isLoading || isMigrationTriggered}\n sx={{ padding: 0, width: 24, height: 24 }}\n >\n \n \n \n )}\n \n \n \n \n \n \n \n \n onClickTeam(e.target.value)} title='Select teams' />\n \n {tempList.map(team => (\n onRemoveTeam(team.id)}> }\n label={team.name}\n onDelete={() => isLoading ? null : onRemoveTeam(team.id)}>\n ))}\n {!tempList.length && \n