diff --git a/cypress/e2e/insights-duplication.cy.ts b/cypress/e2e/insights-duplication.cy.ts index ef5d415e96429..8fb730042677f 100644 --- a/cypress/e2e/insights-duplication.cy.ts +++ b/cypress/e2e/insights-duplication.cy.ts @@ -47,7 +47,7 @@ describe('Insights', () => { cy.get('[data-attr="insight-save-dropdown"]').click() cy.get('[data-attr="insight-save-as-new-insight"]').click() - cy.get('.ant-modal-content .ant-btn-primary').click() + cy.get('button[type=submit]').click() cy.get('[data-attr="top-bar-name"] .EditableField__display').should('contain', `${insightName} (copy)`) savedInsights.checkInsightIsInListView(`${insightName} (copy)`) diff --git a/frontend/src/lib/logic/promptLogic.tsx b/frontend/src/lib/logic/promptLogic.tsx deleted file mode 100644 index d6f78532b8ed0..0000000000000 --- a/frontend/src/lib/logic/promptLogic.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import { Form, FormItemProps, Input, InputProps, Modal, ModalProps } from 'antd' -import { actions, events, kea, key, listeners, path, props } from 'kea' -import { createRoot } from 'react-dom/client' - -import type { promptLogicType } from './promptLogicType' - -/** - * This logic creates a modal to ask for an input. It's unique in that when the logic is unmounted, - * for example when changing the URL, the modal is also closed. That would normally happen with the antd prompt. - * - * @deprecated Use LemonDialog or, more broadly, LemonModal instead. - */ -export const promptLogic = kea([ - path((key) => ['lib', 'logic', 'prompt', key]), - props({} as { key: string }), - key(({ key }) => key), - - actions({ - prompt: ({ title, placeholder, value, error, success, failure }) => ({ - title, - placeholder, - value, - error, - success, - failure, - }), - }), - - events(({ cache }) => ({ - beforeUnmount: [ - () => { - cache.runOnClose && cache.runOnClose() - }, - ], - })), - - listeners(({ cache }) => ({ - prompt: async ({ title, placeholder, value, error, success, failure }) => { - const { cancel, promise } = cancellablePrompt({ - title, - placeholder, - value, - rules: [ - { - required: true, - message: error, - }, - ], - }) - cache.runOnClose = cancel - - try { - const response = await promise - success && success(response) - } catch (err) { - failure && failure(err) - } - }, - })), -]) - -type PromptProps = { - value: string - visible: ModalProps['visible'] - afterClose: ModalProps['afterClose'] - close: ModalProps['onCancel'] - title: ModalProps['title'] - modalProps: ModalProps - rules: FormItemProps['rules'] - placeholder: InputProps['placeholder'] -} - -// Based on https://github.com/wangsijie/antd-prompt/blob/master/src/index.js -// Adapted for ant.design v4 and added cancellation support -function Prompt({ - value, - visible, - afterClose, - close, - title, - modalProps, - rules, - placeholder, -}: PromptProps): JSX.Element { - const [form] = Form.useForm() - const onFinish = (values: Record): void => { - close?.(values.field) - } - - return ( - -
- - - -
-
- ) -} - -function cancellablePrompt(config: Pick): { - cancel: () => void - promise: Promise -} { - let trigger = (): void => {} - const cancel = (): void => { - window.setTimeout(trigger, 1) - } - const promise = new Promise((resolve, reject) => { - const div = document.createElement('div') - const root = createRoot(div) - document.body.appendChild(div) - let currentConfig: PromptProps = { ...config, close, visible: true } as any - - function destroy(value: unknown): void { - root.unmount() - if (div.parentNode) { - div.parentNode.removeChild(div) - } - if (typeof value === 'string') { - resolve(value) - } else { - reject(value) - } - } - - function render(props: PromptProps): void { - root.render() - } - - function close(this: PromptProps, value: string): void { - currentConfig = { - ...currentConfig, - visible: false, - afterClose: destroy.bind(this, value), - } - render(currentConfig) - } - - trigger = close as any - - render(currentConfig) - }) - - return { - cancel, - promise, - } -} diff --git a/frontend/src/models/insightsModel.tsx b/frontend/src/models/insightsModel.tsx index 2047dd546e7f7..e2ecf66a1f595 100644 --- a/frontend/src/models/insightsModel.tsx +++ b/frontend/src/models/insightsModel.tsx @@ -30,23 +30,23 @@ export const insightsModel = kea([ renameInsight: async ({ item }) => { LemonDialog.openForm({ title: 'Rename insight', - initialValues: { name: item.name }, + initialValues: { insightName: item.name }, content: ( - + ), errors: { - name: (name) => (!name ? 'You must enter a name' : undefined), + insightName: (name) => (!name ? 'You must enter a name' : undefined), }, - onSubmit: async ({ name }) => { + onSubmit: async ({ insightName }) => { const updatedItem = await api.update( `api/projects/${teamLogic.values.currentTeamId}/insights/${item.id}`, - { name } + { name: insightName } ) lemonToast.success( <> - Renamed insight from {item.name} to {name} + Renamed insight from {item.name} to {insightName} ) actions.renameInsightSuccess(updatedItem) diff --git a/frontend/src/queries/nodes/HogQLQuery/hogQLQueryEditorLogic.ts b/frontend/src/queries/nodes/HogQLQuery/hogQLQueryEditorLogic.tsx similarity index 92% rename from frontend/src/queries/nodes/HogQLQuery/hogQLQueryEditorLogic.ts rename to frontend/src/queries/nodes/HogQLQuery/hogQLQueryEditorLogic.tsx index b100cd42b1b9d..87a27e3fbd341 100644 --- a/frontend/src/queries/nodes/HogQLQuery/hogQLQueryEditorLogic.ts +++ b/frontend/src/queries/nodes/HogQLQuery/hogQLQueryEditorLogic.tsx @@ -1,8 +1,9 @@ import type { Monaco } from '@monaco-editor/react' +import { LemonDialog, LemonInput } from '@posthog/lemon-ui' import { actions, connect, kea, key, listeners, path, props, propsChanged, reducers, selectors } from 'kea' import { combineUrl } from 'kea-router' import api from 'lib/api' -import { promptLogic } from 'lib/logic/promptLogic' +import { LemonField } from 'lib/lemon-ui/LemonField' // Note: we can oly import types and not values from monaco-editor, because otherwise some Monaco code breaks // auto reload in development. Specifically, on this line: // `export const suggestWidgetStatusbarMenu = new MenuId('suggestWidgetStatusBar')` @@ -45,7 +46,6 @@ export const hogQLQueryEditorLogic = kea([ }), connect({ actions: [dataWarehouseSavedQueriesLogic, ['createDataWarehouseSavedQuery']], - logic: [promptLogic({ key: `save-as-view` })], }), actions({ saveQuery: true, @@ -170,12 +170,18 @@ export const hogQLQueryEditorLogic = kea([ } }, saveAsView: async () => { - promptLogic({ key: `save-as-view` }).actions.prompt({ + LemonDialog.openForm({ title: 'Save as view', - placeholder: 'Please enter the name of the view', - value: '', - error: 'You must enter a name', - success: actions.saveAsViewSuccess, + initialValues: { viewName: '' }, + content: ( + + + + ), + errors: { + viewName: (name) => (!name ? 'You must enter a name' : undefined), + }, + onSubmit: ({ viewName }) => actions.saveAsViewSuccess(viewName), }) }, saveAsViewSuccess: async ({ name }) => { diff --git a/frontend/src/scenes/insights/insightDataLogic.ts b/frontend/src/scenes/insights/insightDataLogic.tsx similarity index 93% rename from frontend/src/scenes/insights/insightDataLogic.ts rename to frontend/src/scenes/insights/insightDataLogic.tsx index 822bfe41dcffb..c48d310c70f6e 100644 --- a/frontend/src/scenes/insights/insightDataLogic.ts +++ b/frontend/src/scenes/insights/insightDataLogic.tsx @@ -1,7 +1,8 @@ +import { LemonDialog, LemonInput } from '@posthog/lemon-ui' import { actions, connect, kea, key, listeners, path, props, propsChanged, reducers, selectors } from 'kea' import { FEATURE_FLAGS } from 'lib/constants' +import { LemonField } from 'lib/lemon-ui/LemonField' import { featureFlagLogic } from 'lib/logic/featureFlagLogic' -import { promptLogic } from 'lib/logic/promptLogic' import { objectsEqual } from 'lib/utils' import { keyForInsightLogicProps } from 'scenes/insights/sharedUtils' import { filterTestAccountsDefaultsLogic } from 'scenes/settings/project/filterTestAccountDefaultsLogic' @@ -79,7 +80,7 @@ export const insightDataLogic = kea([ dataNodeLogic({ key: insightVizDataNodeKey(props) } as DataNodeLogicProps), ['loadData', 'loadDataSuccess', 'loadDataFailure', 'setResponse as setInsightData'], ], - logic: [insightDataTimingLogic(props), promptLogic({ key: `save-as-insight` })], + logic: [insightDataTimingLogic(props)], })), actions({ @@ -256,12 +257,18 @@ export const insightDataLogic = kea([ actions.insightLogicSaveInsight(redirectToViewMode) }, saveAs: async () => { - promptLogic({ key: `save-as-insight` }).actions.prompt({ + LemonDialog.openForm({ title: 'Save as new insight', - placeholder: 'Please enter the new name', - value: `${values.insight.name || values.insight.derived_name} (copy)`, - error: 'You must enter a name', - success: actions.saveAsNamingSuccess, + initialValues: { insightName: `${values.insight.name || values.insight.derived_name} (copy)` }, + content: ( + + + + ), + errors: { + insightName: (name) => (!name ? 'You must enter a name' : undefined), + }, + onSubmit: async ({ insightName }) => actions.saveAsNamingSuccess(insightName), }) }, saveAsNamingSuccess: ({ name }) => {