Skip to content

Commit

Permalink
Merge branch 'master' into by/fix-password-reset-link-generator
Browse files Browse the repository at this point in the history
  • Loading branch information
xrdt authored Mar 20, 2024
2 parents d407fd3 + 4ca3c29 commit 00a9e6c
Show file tree
Hide file tree
Showing 63 changed files with 1,357 additions and 233 deletions.
193 changes: 190 additions & 3 deletions ee/frontend/mobile-replay/__snapshots__/transform.test.ts.snap

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions ee/frontend/mobile-replay/mobile.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,22 @@ export type MobileStyles = {
* @description maps to CSS font-family. Accepts any valid CSS font-family value.
*/
fontFamily?: string
/**
* @description maps to CSS padding-left. Expects a number (treated as pixels) or a string that is a number followed by px e.g. 16px
*/
paddingLeft?: string | number
/**
* @description maps to CSS padding-right. Expects a number (treated as pixels) or a string that is a number followed by px e.g. 16px
*/
paddingRight?: string | number
/**
* @description maps to CSS padding-top. Expects a number (treated as pixels) or a string that is a number followed by px e.g. 16px
*/
paddingTop?: string | number
/**
* @description maps to CSS padding-bottom. Expects a number (treated as pixels) or a string that is a number followed by px e.g. 16px
*/
paddingBottom?: string | number
}

type wireframeBase = {
Expand Down
16 changes: 16 additions & 0 deletions ee/frontend/mobile-replay/schema/mobile/rr-mobile-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,22 @@
"enum": ["left", "right", "center"],
"type": "string"
},
"paddingBottom": {
"description": "maps to CSS padding-bottom. Expects a number (treated as pixels) or a string that is a number followed by px e.g. 16px",
"type": ["string", "number"]
},
"paddingLeft": {
"description": "maps to CSS padding-left. Expects a number (treated as pixels) or a string that is a number followed by px e.g. 16px",
"type": ["string", "number"]
},
"paddingRight": {
"description": "maps to CSS padding-right. Expects a number (treated as pixels) or a string that is a number followed by px e.g. 16px",
"type": ["string", "number"]
},
"paddingTop": {
"description": "maps to CSS padding-top. Expects a number (treated as pixels) or a string that is a number followed by px e.g. 16px",
"type": ["string", "number"]
},
"verticalAlign": {
"description": "vertical alignment with respect to its parent",
"enum": ["top", "bottom", "center"],
Expand Down
32 changes: 32 additions & 0 deletions ee/frontend/mobile-replay/transform.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ describe('replay/transform', () => {
beforeEach(async () => {
posthogEEModule = await posthogEE()
})

test('can process unknown types without error', () => {
expect(
posthogEEModule.mobileReplay?.transformToWeb([
Expand Down Expand Up @@ -535,6 +536,37 @@ describe('replay/transform', () => {
})

describe('inputs', () => {
test('input gets 0 padding by default but can be overridden', () => {
expect(
posthogEEModule.mobileReplay?.transformEventToWeb({
type: 2,
data: {
wireframes: [
{
id: 12359,
width: 100,
height: 30,
type: 'input',
inputType: 'text',
},
{
id: 12361,
width: 100,
height: 30,
type: 'input',
inputType: 'text',
style: {
paddingLeft: '16px',
paddingRight: 16,
},
},
],
},
timestamp: 1,
})
).toMatchSnapshot()
})

test('buttons with nested elements', () => {
expect(
posthogEEModule.mobileReplay?.transformEventToWeb({
Expand Down
1 change: 1 addition & 0 deletions ee/frontend/mobile-replay/transformer/transformers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1383,6 +1383,7 @@ function makeCSSReset(context: ConversionContext): serializedNodeWithId {
border: 0;
outline: 0;
background: transparent;
padding-block: 0 !important;
}
.input:focus {
outline: none;
Expand Down
15 changes: 15 additions & 0 deletions ee/frontend/mobile-replay/transformer/wireframeStyle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,24 @@ function makeLayoutStyles(wireframe: wireframe, styleOverride?: StyleOverride):
}`
)
}

if (styleParts.length) {
styleParts.push(`display: flex`)
}

if (isUnitLike(combinedStyles.paddingLeft)) {
styleParts.push(`padding-left: ${ensureUnit(combinedStyles.paddingLeft)}`)
}
if (isUnitLike(combinedStyles.paddingRight)) {
styleParts.push(`padding-right: ${ensureUnit(combinedStyles.paddingRight)}`)
}
if (isUnitLike(combinedStyles.paddingTop)) {
styleParts.push(`padding-top: ${ensureUnit(combinedStyles.paddingTop)}`)
}
if (isUnitLike(combinedStyles.paddingBottom)) {
styleParts.push(`padding-bottom: ${ensureUnit(combinedStyles.paddingBottom)}`)
}

return asStyleString(styleParts)
}

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,24 @@ MultipleSelect.args = {

export const MultipleSelectWithCustom: Story = Template.bind({})
MultipleSelectWithCustom.args = {
placeholder: 'Enter any email...',
placeholder: 'Pick a url...',
mode: 'multiple',
allowCustomValues: true,
options: [
{
key: 'http://posthog.com/docs',
label: 'http://posthog.com/docs',
},
{
key: 'http://posthog.com/pricing',
label: 'http://posthog.com/pricing',
},

{
key: 'http://posthog.com/products',
label: 'http://posthog.com/products',
},
],
}

export const Disabled: Story = Template.bind({})
Expand Down
145 changes: 102 additions & 43 deletions frontend/src/lib/lemon-ui/LemonInputSelect/LemonInputSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Tooltip } from '@posthog/lemon-ui'
import { useKeyHeld } from 'lib/hooks/useKeyHeld'
import { LemonSkeleton } from 'lib/lemon-ui/LemonSkeleton'
import { LemonSnack } from 'lib/lemon-ui/LemonSnack/LemonSnack'
import { range } from 'lib/utils'
Expand Down Expand Up @@ -49,14 +51,21 @@ export function LemonInputSelect({
const inputRef = useRef<HTMLInputElement>(null)
const [selectedIndex, setSelectedIndex] = useState(0)
const values = value ?? []
const altKeyHeld = useKeyHeld('Alt')

const separateOnComma = allowCustomValues && mode === 'multiple'

const visibleOptions = useMemo(() => {
const res: LemonInputSelectOption[] = []
const customValues = [...values]

// We show the input value if custom values are allowed and it's not in the list
if (allowCustomValues && inputValue && !values.includes(inputValue)) {
customValues.unshift(inputValue)
}

options.forEach((option) => {
// Remove from the custom values list if it's in the options

if (customValues.includes(option.key)) {
customValues.splice(customValues.indexOf(option.key), 1)
}
Expand All @@ -75,48 +84,78 @@ export function LemonInputSelect({
res.unshift({ key: value, label: value })
})
}

// Finally we show the input value if custom values are allowed and it's not in the list
if (allowCustomValues && inputValue && !values.includes(inputValue)) {
res.unshift({ key: inputValue, label: inputValue })
}

return res
}, [options, inputValue, value])
}, [options, inputValue, values])

// Reset the selected index when the visible options change
useEffect(() => {
setSelectedIndex(0)
}, [visibleOptions.length])

const setInputValue = (newValue: string): void => {
// Special case for multiple mode with custom values
if (separateOnComma && newValue.includes(',')) {
const newValues = [...values]

newValue.split(',').forEach((value) => {
const trimmedValue = value.trim()
if (trimmedValue && !values.includes(trimmedValue)) {
newValues.push(trimmedValue)
}
})

onChange?.(newValues)
newValue = ''
}

_setInputValue(newValue)
onInputChange?.(inputValue)
}

const _onActionItem = (item: string): void => {
const _removeItem = (item: string): void => {
let newValues = [...values]
if (values.includes(item)) {
// Remove the item
if (mode === 'single') {
newValues = []
} else {
newValues.splice(values.indexOf(item), 1)
}
// Remove the item
if (mode === 'single') {
newValues = []
} else {
// Add the item
if (mode === 'single') {
newValues = [item]
} else {
newValues.splice(values.indexOf(item), 1)
}

onChange?.(newValues)
}

const _addItem = (item: string): void => {
let newValues = [...values]
// Add the item
if (mode === 'single') {
newValues = [item]
} else {
if (!newValues.includes(item)) {
newValues.push(item)
}

setInputValue('')
}

setInputValue('')
onChange?.(newValues)
}

const _onActionItem = (item: string): void => {
if (altKeyHeld && allowCustomValues) {
// In this case we want to remove it if added and set input to it
if (values.includes(item)) {
_removeItem(item)
}
setInputValue(item)
return
}

if (values.includes(item)) {
_removeItem(item)
} else {
_addItem(item)
}
}

const _onBlur = (): void => {
// We need to add a delay as a click could be in the popover or the input wrapper which refocuses
setTimeout(() => {
Expand All @@ -143,8 +182,8 @@ export function LemonInputSelect({
const _onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>): void => {
if (e.key === 'Enter') {
e.preventDefault()

const itemToAdd = visibleOptions[selectedIndex]?.key

if (itemToAdd) {
_onActionItem(visibleOptions[selectedIndex]?.key)
}
Expand All @@ -164,33 +203,51 @@ export function LemonInputSelect({
}
}

// TRICKY: We don't want the popover to affect the snack buttons
const prefix = (
<PopoverReferenceContext.Provider value={null}>
<>
{values.map((value) => {
const option = options.find((option) => option.key === value) ?? {
label: value,
labelComponent: null,
}
return (
<>
<LemonSnack title={option?.label} onClose={() => _onActionItem(value)}>
const prefix = useMemo(
() => (
// TRICKY: We don't want the popover to affect the snack buttons
<PopoverReferenceContext.Provider value={null}>
<>
{values.map((value) => {
const option = options.find((option) => option.key === value) ?? {
label: value,
labelComponent: null,
}
const snack = (
<LemonSnack
title={option?.label}
onClose={() => _onActionItem(value)}
onClick={allowCustomValues ? () => _onActionItem(value) : undefined}
>
{option?.labelComponent ?? option?.label}
</LemonSnack>
</>
)
})}
</>
</PopoverReferenceContext.Provider>
)
return allowCustomValues ? (
<Tooltip
title={
<>
<KeyboardShortcut option /> + click to edit
</>
}
>
{snack}
</Tooltip>
) : (
snack
)
})}
</>
</PopoverReferenceContext.Provider>
),
[values, options, altKeyHeld, allowCustomValues]
)

return (
<LemonDropdown
closeOnClickInside={false}
visible={showPopover}
sameWidth
closeOnClickInside={false}
actionable
visible={showPopover}
onClickOutside={() => {
popoverFocusRef.current = false
setShowPopover(false)
Expand Down Expand Up @@ -219,7 +276,9 @@ export function LemonInputSelect({
{isHighlighted ? (
<span>
<KeyboardShortcut enter />{' '}
{!values.includes(option.key)
{altKeyHeld && allowCustomValues
? 'edit'
: !values.includes(option.key)
? mode === 'single'
? 'select'
: 'add'
Expand Down
10 changes: 8 additions & 2 deletions frontend/src/queries/nodes/InsightViz/Breakdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import { insightVizDataLogic } from 'scenes/insights/insightVizDataLogic'
import { EditorFilterProps } from '~/types'

export function Breakdown({ insightProps }: EditorFilterProps): JSX.Element {
const { breakdownFilter, display, isTrends, isDataWarehouseSeries } = useValues(insightVizDataLogic(insightProps))
const { breakdownFilter, display, isTrends, isSingleSeries, isDataWarehouseSeries } = useValues(
insightVizDataLogic(insightProps)
)
const { updateBreakdownFilter, updateDisplay } = useActions(insightVizDataLogic(insightProps))

return (
Expand All @@ -14,9 +16,13 @@ export function Breakdown({ insightProps }: EditorFilterProps): JSX.Element {
breakdownFilter={breakdownFilter}
display={display}
isTrends={isTrends}
isDataWarehouseSeries={isDataWarehouseSeries}
updateBreakdownFilter={updateBreakdownFilter}
updateDisplay={updateDisplay}
disabledReason={
!isSingleSeries && isDataWarehouseSeries
? 'Breakdowns are not allowed for multiple series types'
: undefined
}
/>
)
}
Loading

0 comments on commit 00a9e6c

Please sign in to comment.