diff --git a/frontend/src/layout/navigation-3000/components/TopBar.tsx b/frontend/src/layout/navigation-3000/components/TopBar.tsx
index 5a8bc2f2a235c..af640b56fcf41 100644
--- a/frontend/src/layout/navigation-3000/components/TopBar.tsx
+++ b/frontend/src/layout/navigation-3000/components/TopBar.tsx
@@ -237,6 +237,7 @@ function Here({ breadcrumb }: HereProps): JSX.Element {
placeholder="Unnamed"
compactButtons="xsmall"
editingIndication="underlined"
+ autoFocus
/>
) : (
{breadcrumb.name}
diff --git a/frontend/src/lib/components/EditableField/EditableField.tsx b/frontend/src/lib/components/EditableField/EditableField.tsx
index 3b25fd7a4be8e..e8cc91c67ef5e 100644
--- a/frontend/src/lib/components/EditableField/EditableField.tsx
+++ b/frontend/src/lib/components/EditableField/EditableField.tsx
@@ -1,12 +1,13 @@
import './EditableField.scss'
+import { useMergeRefs } from '@floating-ui/react'
import clsx from 'clsx'
import { IconEdit, IconMarkdown } from 'lib/lemon-ui/icons'
import { LemonButton } from 'lib/lemon-ui/LemonButton'
import { LemonMarkdown } from 'lib/lemon-ui/LemonMarkdown'
import { Tooltip } from 'lib/lemon-ui/Tooltip'
import { pluralize } from 'lib/utils'
-import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
+import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'
import TextareaAutosize from 'react-textarea-autosize'
export interface EditableFieldProps {
@@ -51,7 +52,7 @@ export function EditableField({
placeholder,
minLength,
maxLength,
- autoFocus = true,
+ autoFocus = false,
multiline = false,
markdown = false,
compactButtons = false,
@@ -65,16 +66,30 @@ export function EditableField({
saveButtonText = 'Save',
notice,
}: EditableFieldProps): JSX.Element {
- const [localIsEditing, setLocalIsEditing] = useState(false)
+ const [localIsEditing, setLocalIsEditing] = useState(mode === 'edit')
const [localTentativeValue, setLocalTentativeValue] = useState(value)
+ const inputRef = useRef()
+ const previousIsEditing = useRef()
useEffect(() => {
setLocalTentativeValue(value)
}, [value])
+
useEffect(() => {
setLocalIsEditing(mode === 'edit')
}, [mode])
+ useEffect(() => {
+ // We always want to focus when switching to edit mode, but can't use autoFocus, because we don't want this to
+ // happen when the component is _initially_ rendered in edit mode.
+ if (inputRef.current && previousIsEditing.current === false && localIsEditing === true) {
+ const endOfInput = inputRef.current.value.length
+ inputRef.current.setSelectionRange(endOfInput, endOfInput)
+ inputRef.current.focus()
+ }
+ previousIsEditing.current = localIsEditing
+ }, [localIsEditing])
+
const isSaveable = !minLength || localTentativeValue.length >= minLength
const mouseDownOnCancelButton = (e: React.MouseEvent): void => {
@@ -150,6 +165,7 @@ export function EditableField({
minLength={minLength}
maxLength={maxLength}
autoFocus={autoFocus}
+ ref={inputRef as React.RefObject}
/>
) : (
}
/>
)}
{(!mode || !!onModeToggle) && (
@@ -242,17 +259,7 @@ export function EditableField({
)
}
-const AutosizeInput = ({
- name,
- value,
- onChange,
- placeholder,
- onBlur,
- onKeyDown,
- minLength,
- maxLength,
- autoFocus,
-}: {
+interface AutosizeInputProps {
name: string
value: string
onChange: (e: React.ChangeEvent) => void
@@ -262,11 +269,18 @@ const AutosizeInput = ({
minLength?: number
maxLength?: number
autoFocus?: boolean
-}): JSX.Element => {
+}
+
+const AutosizeInput = React.forwardRef(function AutosizeInput(
+ { name, value, onChange, placeholder, onBlur, onKeyDown, minLength, maxLength, autoFocus },
+ ref
+) {
const [inputWidth, setInputWidth] = useState(1)
- const inputRef = useRef(null)
+ const [inputStyles, setInputStyles] = useState()
const sizerRef = useRef(null)
const placeHolderSizerRef = useRef(null)
+ const inputRef = useRef(null)
+ const mergedRefs = useMergeRefs([ref, inputRef])
const copyStyles = (styles: CSSStyleDeclaration, node: HTMLDivElement): void => {
node.style.fontSize = styles.fontSize
@@ -277,21 +291,22 @@ const AutosizeInput = ({
node.style.textTransform = styles.textTransform
}
- const inputStyles = useMemo(() => {
- return inputRef.current ? window.getComputedStyle(inputRef.current) : null
- }, [inputRef.current])
-
useLayoutEffect(() => {
- if (inputStyles && placeHolderSizerRef.current) {
- copyStyles(inputStyles, placeHolderSizerRef.current)
+ if (inputRef.current) {
+ setInputStyles(getComputedStyle(inputRef.current))
}
- }, [placeHolderSizerRef, placeHolderSizerRef])
+ }, [inputRef.current])
useLayoutEffect(() => {
- if (inputStyles && sizerRef.current) {
- copyStyles(inputStyles, sizerRef.current)
+ if (inputStyles) {
+ if (sizerRef.current) {
+ copyStyles(inputStyles, sizerRef.current)
+ }
+ if (placeHolderSizerRef.current) {
+ copyStyles(inputStyles, placeHolderSizerRef.current)
+ }
}
- }, [inputStyles, sizerRef])
+ }, [inputStyles])
useLayoutEffect(() => {
if (!sizerRef.current || !placeHolderSizerRef.current) {
@@ -320,7 +335,7 @@ const AutosizeInput = ({
minLength={minLength}
maxLength={maxLength}
autoFocus={autoFocus}
- ref={inputRef}
+ ref={mergedRefs}
/* eslint-disable-next-line react/forbid-dom-props */
style={{ boxSizing: 'content-box', width: `${inputWidth}px` }}
/>
@@ -332,4 +347,4 @@ const AutosizeInput = ({
)
-}
+})
diff --git a/frontend/src/scenes/actions/actionEditLogic.tsx b/frontend/src/scenes/actions/actionEditLogic.tsx
index 5680eea4ae1c8..5d3c8d193285f 100644
--- a/frontend/src/scenes/actions/actionEditLogic.tsx
+++ b/frontend/src/scenes/actions/actionEditLogic.tsx
@@ -69,9 +69,6 @@ export const actionEditLogic = kea([
forms(({ actions, props }) => ({
action: {
defaults: { ...props.action } as ActionEditType,
- errors: ({ name }) => ({
- name: !name ? 'You need to set a name' : null,
- }),
submit: (action) => {
actions.saveAction(action)
},
diff --git a/frontend/src/scenes/data-management/actions/ActionsTable.tsx b/frontend/src/scenes/data-management/actions/ActionsTable.tsx
index 8efad967b31b3..997c7f0378c1c 100644
--- a/frontend/src/scenes/data-management/actions/ActionsTable.tsx
+++ b/frontend/src/scenes/data-management/actions/ActionsTable.tsx
@@ -47,7 +47,7 @@ export function ActionsTable(): JSX.Element {
return (
<>
- {name || Unnamed action}
+ {name || Unnamed}
{action.description && (
diff --git a/frontend/src/scenes/insights/insightSceneLogic.tsx b/frontend/src/scenes/insights/insightSceneLogic.tsx
index 8a8f1fda9cd0e..eb55a25fa0aa0 100644
--- a/frontend/src/scenes/insights/insightSceneLogic.tsx
+++ b/frontend/src/scenes/insights/insightSceneLogic.tsx
@@ -9,13 +9,17 @@ import { preflightLogic } from 'scenes/PreflightCheck/preflightLogic'
import { sceneLogic } from 'scenes/sceneLogic'
import { Scene } from 'scenes/sceneTypes'
import { teamLogic } from 'scenes/teamLogic'
+import { mathsLogic } from 'scenes/trends/mathsLogic'
import { urls } from 'scenes/urls'
+import { cohortsModel } from '~/models/cohortsModel'
+import { groupsModel } from '~/models/groupsModel'
import { Breadcrumb, FilterType, InsightShortId, InsightType, ItemMode } from '~/types'
import { insightDataLogic } from './insightDataLogic'
import { insightDataLogicType } from './insightDataLogicType'
import type { insightSceneLogicType } from './insightSceneLogicType'
+import { summarizeInsight } from './summarizeInsight'
export const insightSceneLogic = kea([
path(['scenes', 'insights', 'insightSceneLogic']),
@@ -85,10 +89,19 @@ export const insightSceneLogic = kea([
}),
selectors(() => ({
insightSelector: [(s) => [s.insightLogicRef], (insightLogicRef) => insightLogicRef?.logic.selectors.insight],
+ filtersSelector: [(s) => [s.insightLogicRef], (insightLogicRef) => insightLogicRef?.logic.selectors.filters],
insight: [(s) => [(state, props) => s.insightSelector?.(state, props)?.(state, props)], (insight) => insight],
+ filters: [(s) => [(state, props) => s.filtersSelector?.(state, props)?.(state, props)], (filters) => filters],
breadcrumbs: [
- (s) => [s.insight, s.insightLogicRef],
- (insight, insightLogicRef): Breadcrumb[] => [
+ (s) => [
+ s.insightLogicRef,
+ s.insight,
+ s.filters,
+ groupsModel.selectors.aggregationLabel,
+ cohortsModel.selectors.cohortsById,
+ mathsLogic.selectors.mathDefinitions,
+ ],
+ (insightLogicRef, insight, filters, aggregationLabel, cohortsById, mathDefinitions): Breadcrumb[] => [
{
key: Scene.SavedInsights,
name: 'Product analytics',
@@ -96,7 +109,13 @@ export const insightSceneLogic = kea([
},
{
key: insight?.short_id || 'new',
- name: insight?.name || insight?.derived_name || 'Unnamed',
+ name:
+ insight?.name ||
+ summarizeInsight(insight?.query, filters, {
+ aggregationLabel,
+ cohortsById,
+ mathDefinitions,
+ }),
onRename: async (name: string) => {
await insightLogicRef?.logic.asyncActions.setInsightMetadata({ name })
},
diff --git a/frontend/src/scenes/insights/summarizeInsight.ts b/frontend/src/scenes/insights/summarizeInsight.ts
index ef9e4834854c6..c74293d616e15 100644
--- a/frontend/src/scenes/insights/summarizeInsight.ts
+++ b/frontend/src/scenes/insights/summarizeInsight.ts
@@ -343,15 +343,14 @@ export interface SummaryContext {
export function summarizeInsight(
query: Node | undefined | null,
- filters: Partial,
+ filters: Partial | undefined | null,
context: SummaryContext
): string {
- const hasFilters = Object.keys(filters || {}).length > 0
return isInsightVizNode(query)
? summarizeInsightQuery(query.source, context)
: !!query && !isInsightVizNode(query)
? summarizeQuery(query)
- : hasFilters
+ : filters && Object.keys(filters).length > 0
? summarizeInsightFilters(filters, context)
: ''
}
diff --git a/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistSceneLogic.ts b/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistSceneLogic.ts
index ade9799b123d6..6d7c55e88acc1 100644
--- a/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistSceneLogic.ts
+++ b/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistSceneLogic.ts
@@ -4,6 +4,7 @@ import { actions, afterMount, connect, kea, key, listeners, path, props, reducer
import { loaders } from 'kea-loaders'
import { beforeUnload, router } from 'kea-router'
import api from 'lib/api'
+import { sceneLogic } from 'scenes/sceneLogic'
import { Scene } from 'scenes/sceneTypes'
import {
deletePlaylist,
@@ -30,7 +31,7 @@ export const sessionRecordingsPlaylistSceneLogic = kea props.shortId),
connect({
- values: [cohortsModel, ['cohortsById']],
+ values: [cohortsModel, ['cohortsById'], sceneLogic, ['activeScene']],
}),
actions({
updatePlaylist: (properties?: Partial, silent = false) => ({
@@ -126,7 +127,10 @@ export const sessionRecordingsPlaylistSceneLogic = kea ({
- enabled: (newLocation) => values.hasChanges && newLocation?.pathname !== router.values.location.pathname,
+ enabled: (newLocation) =>
+ values.activeScene === Scene.ReplayPlaylist &&
+ values.hasChanges &&
+ newLocation?.pathname !== router.values.location.pathname,
message: 'Leave playlist?\nChanges you made will be discarded.',
onConfirm: () => {
actions.setFilters(values.playlist?.filters || null)