diff --git a/frontend/app/components/Dashboard/components/AddCardSelectionModal.tsx b/frontend/app/components/Dashboard/components/AddCardSelectionModal.tsx index 302f25b52..609f2a336 100644 --- a/frontend/app/components/Dashboard/components/AddCardSelectionModal.tsx +++ b/frontend/app/components/Dashboard/components/AddCardSelectionModal.tsx @@ -1,60 +1,93 @@ +import { Card, Col, Modal, Row, Typography } from 'antd'; +import { GalleryVertical, Plus } from 'lucide-react'; import React from 'react'; -import {Card, Col, Modal, Row, Typography} from "antd"; -import {GalleryVertical, Plus} from "lucide-react"; -import NewDashboardModal from "Components/Dashboard/components/DashboardList/NewDashModal"; -import {useStore} from "App/mstore"; + +import { useStore } from 'App/mstore'; +import NewDashboardModal from 'Components/Dashboard/components/DashboardList/NewDashModal'; + +import AiQuery from './DashboardView/AiQuery'; interface Props { - open: boolean; - onClose?: () => void; + open: boolean; + onClose?: () => void; } function AddCardSelectionModal(props: Props) { - const {metricStore} = useStore(); - const [open, setOpen] = React.useState(false); - const [isLibrary, setIsLibrary] = React.useState(false); + const { metricStore } = useStore(); + const [open, setOpen] = React.useState(false); + const [isLibrary, setIsLibrary] = React.useState(false); - const onCloseModal = () => { - setOpen(false); - props.onClose && props.onClose(); - } + const onCloseModal = () => { + setOpen(false); + props.onClose && props.onClose(); + }; - const onClick = (isLibrary: boolean) => { - if (!isLibrary) { - metricStore.init(); - } - setIsLibrary(isLibrary); - setOpen(true); + const onClick = (isLibrary: boolean) => { + if (!isLibrary) { + metricStore.init(); } - return ( - <> - + + {isSaas ? ( + <> + + + +
- - -
onClick(true)}> - - Add from library - {/*

Select from 12 available

*/} -
- - - -
onClick(false)}> - - Create New -
- -
- - - - ); + or +
+ + ) : null} + + +
onClick(true)} + > + + Add from library +
+ + +
onClick(false)} + > + + Create New +
+ +
+
+ + + ); } export default AddCardSelectionModal; diff --git a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/CreateCard.tsx b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/CreateCard.tsx index 9f86260a3..c0caf656c 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/CreateCard.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/CreateCard.tsx @@ -20,7 +20,8 @@ const getTitleByType = (type: string) => { interface Props { // cardType: string, - onBack: () => void + onBack?: () => void + onAdded?: () => void } function CreateCard(props: Props) { @@ -67,7 +68,8 @@ function CreateCard(props: Props) { if (dashboardId) { await addCardToDashboard(dashboardId, cardId); - dashboardStore.fetch(dashboardId); + void dashboardStore.fetch(dashboardId); + props.onAdded?.(); } else if (isItDashboard) { const dashboardId = await createNewDashboard(); await addCardToDashboard(dashboardId, cardId); @@ -81,9 +83,9 @@ function CreateCard(props: Props) {
- + {props.onBack ? : null}
{metric.name}
diff --git a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/NewDashboardModal.tsx b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/NewDashboardModal.tsx index c1301765d..d5a22f1fb 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/NewDashboardModal.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/NewDashboardModal.tsx @@ -1,9 +1,11 @@ -import React, { useEffect } from 'react'; import { Modal } from 'antd'; -import SelectCard from './SelectCard'; -import CreateCard from 'Components/Dashboard/components/DashboardList/NewDashModal/CreateCard'; -import colors from 'tailwindcss/colors'; +import React, { useEffect } from 'react'; import { connect } from 'react-redux'; +import colors from 'tailwindcss/colors'; + +import CreateCard from 'Components/Dashboard/components/DashboardList/NewDashModal/CreateCard'; + +import SelectCard from './SelectCard'; interface NewDashboardModalProps { onClose: () => void; @@ -14,14 +16,15 @@ interface NewDashboardModalProps { } const NewDashboardModal: React.FC = ({ - onClose, - open, - isAddingFromLibrary = false, - isEnterprise = false, - isMobile = false - }) => { + onClose, + open, + isAddingFromLibrary = false, + isEnterprise = false, + isMobile = false, +}) => { const [step, setStep] = React.useState(0); - const [selectedCategory, setSelectedCategory] = React.useState('product-analytics'); + const [selectedCategory, setSelectedCategory] = + React.useState('product-analytics'); useEffect(() => { return () => { @@ -40,35 +43,42 @@ const NewDashboardModal: React.FC = ({ closeIcon={false} styles={{ content: { - backgroundColor: colors.gray[100] - } + backgroundColor: colors.gray[100], + }, }} centered={true} > -
- {step === 0 && setStep(step + 1)} - isLibrary={isAddingFromLibrary} - isMobile={isMobile} - isEnterprise={isEnterprise} />} +
+ {step === 0 && ( + setStep(step + 1)} + isLibrary={isAddingFromLibrary} + isMobile={isMobile} + isEnterprise={isEnterprise} + /> + )} {step === 1 && setStep(0)} />}
- ) - ; + ); }; const mapStateToProps = (state: any) => ({ isMobile: state.getIn(['site', 'instance', 'platform']) === 'ios', - isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee' || - state.getIn(['user', 'account', 'edition']) === 'msaas' + isEnterprise: + state.getIn(['user', 'account', 'edition']) === 'ee' || + state.getIn(['user', 'account', 'edition']) === 'msaas', }); export default connect(mapStateToProps)(NewDashboardModal); diff --git a/frontend/app/components/Dashboard/components/DashboardView/AiQuery.tsx b/frontend/app/components/Dashboard/components/DashboardView/AiQuery.tsx new file mode 100644 index 000000000..2d3576cfc --- /dev/null +++ b/frontend/app/components/Dashboard/components/DashboardView/AiQuery.tsx @@ -0,0 +1,129 @@ +import { SendOutlined } from '@ant-design/icons'; +import { Modal } from 'antd'; +import Lottie from 'lottie-react'; +import { observer } from 'mobx-react-lite'; +import React from 'react'; +import colors from 'tailwindcss/colors'; + +import { gradientBox } from 'App/components/shared/SessionSearchField/AiSessionSearchField'; +import aiSpinner from 'App/lottie/aiSpinner.json'; +import { useStore } from 'App/mstore'; +import { Icon, Input } from 'UI'; + +import CreateCard from '../DashboardList/NewDashModal/CreateCard'; + +function AiQuery() { + const grad = { + background: 'linear-gradient(90deg, #F3F4FF 0%, #F2FEFF 100%)', + }; + return ( + <> + +
+ +
+ + ); +} + +const InputBox = observer(({ inModal }: { inModal?: boolean }) => { + const { aiFiltersStore, metricStore } = useStore(); + const metric = metricStore.instance; + const fetchResults = () => { + aiFiltersStore + .getCardFilters(aiFiltersStore.query, undefined) + .then((f) => metric.createSeries(f.filters)); + if (!inModal) { + aiFiltersStore.setModalOpen(true); + } + }; + return ( + <> +
+ +
What would you like to visualize?
+
+
+ aiFiltersStore.setQuery(target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter' && aiFiltersStore.query.trim().length > 2) { + fetchResults(); + } + }} + placeholder={'E.g., Track all the errors in checkout flow.'} + className="ml-2 px-2 pe-9 text-lg placeholder-lg !border-0 rounded-e-full nofocus" + leadingButton={ + aiFiltersStore.query !== '' ? ( +
+
+ +
+
+ ) : null + } + /> +
+ + ); +}); + +const QueryModal = observer(() => { + const { aiFiltersStore } = useStore(); + + const onClose = () => { + aiFiltersStore.setModalOpen(false); + }; + return ( + +
+ + {aiFiltersStore.isLoading ? ( + + ) : ( + + )} +
+
+ ); +}); + +function Loader() { + return ( +
+
+ +
+
AI is brewing your card, wait a few seconds...
+
+ ); +} + +export default observer(AiQuery); diff --git a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx index ffea3f484..1a2d597e7 100644 --- a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx +++ b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx @@ -13,6 +13,7 @@ import withPageTitle from 'HOCs/withPageTitle'; import withReport from 'App/components/hocs/withReport'; import DashboardHeader from '../DashboardHeader'; import {useHistory} from "react-router"; +import AiQuery from "./AiQuery"; interface IProps { siteId: string; @@ -91,12 +92,16 @@ function DashboardView(props: Props) { if (!dashboard) return null; + const originStr = window.env.ORIGIN || window.location.origin; + const testingKey = localStorage.getItem('__mauricio_testing_access') === 'true'; + + const isSaas = testingKey && /app\.openreplay\.com/.test(originStr); return (
{/* @ts-ignore */} - + {isSaas ? : null} i.type === 'table'); diff --git a/frontend/app/components/shared/SessionSearchField/AiSessionSearchField.tsx b/frontend/app/components/shared/SessionSearchField/AiSessionSearchField.tsx index 3cb28726a..7d5111f40 100644 --- a/frontend/app/components/shared/SessionSearchField/AiSessionSearchField.tsx +++ b/frontend/app/components/shared/SessionSearchField/AiSessionSearchField.tsx @@ -313,7 +313,7 @@ export const AskAiSwitchToggle = ({ ); }; -const gradientBox = { +export const gradientBox = { border: 'double 1.5px transparent', borderRadius: '100px', background: diff --git a/frontend/app/components/ui/Input/Input.tsx b/frontend/app/components/ui/Input/Input.tsx index 89698cb79..9e93376c8 100644 --- a/frontend/app/components/ui/Input/Input.tsx +++ b/frontend/app/components/ui/Input/Input.tsx @@ -1,45 +1,72 @@ -import React from 'react'; import cn from 'classnames'; +import React from 'react'; + import { Icon } from 'UI'; interface Props { - wrapperClassName?: string; - className?: string; - icon?: string; - leadingButton?: React.ReactNode; - type?: string; - rows?: number; - height?: number; - width?: number; - [x: string]: any; + wrapperClassName?: string; + className?: string; + icon?: string; + leadingButton?: React.ReactNode; + type?: string; + rows?: number; + height?: number; + width?: number; + [x: string]: any; } const Input = React.forwardRef((props: Props, ref: any) => { - const { height = 36, width = 0, className = '', leadingButton = '', wrapperClassName = '', icon = '', type = 'text', rows = 4, ...rest } = props; - return ( -
- {icon && } - {type === 'textarea' ? ( -