Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(insights): add custom data color themes #26350

Closed
wants to merge 55 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
2fd38ce
scaffold data color theme setting
thmsobrmlr Nov 21, 2024
d9cb7c6
more scaffolding
thmsobrmlr Nov 21, 2024
1cd9607
add default theme
thmsobrmlr Nov 21, 2024
f4f7c56
display theme from backend
thmsobrmlr Nov 21, 2024
127fe5d
separate themes and theme colors
thmsobrmlr Nov 21, 2024
9f9d442
open modal with default theme
thmsobrmlr Nov 21, 2024
40b43ea
wip separating modal and form logic
thmsobrmlr Nov 21, 2024
d1ee461
initial form working
thmsobrmlr Nov 21, 2024
69656a3
make call to api
thmsobrmlr Nov 21, 2024
cce17f0
make call to api
thmsobrmlr Nov 21, 2024
f34ca7a
Update UI snapshots for `chromium` (1)
github-actions[bot] Nov 22, 2024
fe5ea15
fix constraint in migration and clean up
thmsobrmlr Nov 22, 2024
3c3a84a
add ability to override default theme
thmsobrmlr Nov 22, 2024
711821a
add basic admin interface for data color themes
thmsobrmlr Nov 22, 2024
04c8f9c
basic theme overrides
thmsobrmlr Nov 22, 2024
0c6a2b4
add storybook story for data colors
thmsobrmlr Nov 22, 2024
5e3efc6
allow setting custom theme on insight and add getTrendsColor selector
thmsobrmlr Nov 22, 2024
4a07588
wip
thmsobrmlr Nov 22, 2024
47f0572
wip
thmsobrmlr Nov 22, 2024
18d6080
make dataColorTheme visualization-only
thmsobrmlr Nov 22, 2024
ae2f49f
cleanup
thmsobrmlr Nov 22, 2024
ae1ba0e
Update query snapshots
github-actions[bot] Nov 22, 2024
2bc6c57
Update query snapshots
github-actions[bot] Nov 22, 2024
6627d50
Update query snapshots
github-actions[bot] Nov 22, 2024
30f1f98
Update UI snapshots for `chromium` (1)
github-actions[bot] Nov 22, 2024
272cacf
Update query snapshots
github-actions[bot] Nov 22, 2024
c0ccc4b
add icon for global themes
thmsobrmlr Nov 22, 2024
fd5d4cf
add ColorGlyph component
thmsobrmlr Nov 22, 2024
4256918
"add theme" does not fill name and instead has a placeholder and auto…
thmsobrmlr Nov 22, 2024
218f283
handle dirty forms and allow closing modal
thmsobrmlr Nov 22, 2024
cbe709f
make update work
thmsobrmlr Nov 22, 2024
d845843
allow adding and removing colors
thmsobrmlr Nov 22, 2024
50b0f45
allow duplicating color
thmsobrmlr Nov 22, 2024
6b9bd45
add permissions, tests and a couple of metadata columns
thmsobrmlr Nov 22, 2024
07c12af
make global themes un-editable
thmsobrmlr Nov 25, 2024
4605035
remove unused import
thmsobrmlr Nov 25, 2024
f10b23f
Update UI snapshots for `chromium` (2)
github-actions[bot] Nov 25, 2024
6f18439
Update UI snapshots for `chromium` (2)
github-actions[bot] Nov 25, 2024
0c48c8c
add error handling
thmsobrmlr Nov 26, 2024
5c60d12
wip
thmsobrmlr Nov 27, 2024
60b45a1
remove unique name constraint
thmsobrmlr Nov 28, 2024
3a0c3dc
fix rendering custom themes on page load
thmsobrmlr Nov 28, 2024
5672623
allow fetching relevant theme data from shared insights
thmsobrmlr Nov 28, 2024
a730604
expose team's default data theme to shared insights
thmsobrmlr Nov 28, 2024
62b8b59
wait for theme to be loaded
thmsobrmlr Nov 28, 2024
b133045
hide setting when flag is disabled
thmsobrmlr Nov 28, 2024
e25394f
clean up types
thmsobrmlr Nov 28, 2024
5254f8c
move DataColorThemeModel to types
thmsobrmlr Nov 28, 2024
789e945
fix lint issue
thmsobrmlr Nov 28, 2024
9055c15
fix lint issues
thmsobrmlr Nov 28, 2024
86ba2c9
fix lint issues
thmsobrmlr Nov 29, 2024
92ee4d7
fix opening modal when legend is open
thmsobrmlr Nov 28, 2024
fc6da21
fix types
thmsobrmlr Nov 29, 2024
528059d
fix types
thmsobrmlr Nov 29, 2024
bd79600
fix some lint issues
thmsobrmlr Dec 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
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.
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.
22 changes: 22 additions & 0 deletions frontend/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
DashboardTemplateListParams,
DashboardTemplateType,
DashboardType,
DataColorThemeModel,
DataWarehouseSavedQuery,
DataWarehouseTable,
DataWarehouseViewLink,
Expand Down Expand Up @@ -913,6 +914,15 @@ class ApiRequest {
public async delete(): Promise<any> {
return await api.delete(this.assembleFullUrl())
}

// Data color themes
public dataColorThemes(teamId?: TeamType['id']): ApiRequest {
return this.environmentsDetail(teamId).addPathComponent('data_color_themes')
}

public dataColorTheme(id: DataColorThemeModel['id'], teamId?: TeamType['id']): ApiRequest {
return this.environmentsDetail(teamId).addPathComponent('data_color_themes').addPathComponent(id)
}
}

const normalizeUrl = (url: string): string => {
Expand Down Expand Up @@ -2456,6 +2466,18 @@ const api = {
},
},

dataColorThemes: {
async list(): Promise<DataColorThemeModel[]> {
return await new ApiRequest().dataColorThemes().get()
},
async create(data: Partial<DataColorThemeModel>): Promise<DataColorThemeModel> {
return await new ApiRequest().dataColorThemes().create({ data })
},
async update(id: DataColorThemeModel['id'], data: Partial<DataColorThemeModel>): Promise<DataColorThemeModel> {
return await new ApiRequest().dataColorTheme(id).update({ data })
},
},

queryURL: (): string => {
return new ApiRequest().query().assembleFullUrl(true)
},
Expand Down
24 changes: 6 additions & 18 deletions frontend/src/lib/components/InsightLegend/InsightLegendRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ import { getSeriesBackgroundColor } from 'lib/colors'
import { InsightLabel } from 'lib/components/InsightLabel'
import { LemonCheckbox } from 'lib/lemon-ui/LemonCheckbox'
import { useEffect, useRef } from 'react'
import { dataThemeLogic } from 'scenes/dataThemeLogic'
import { formatAggregationAxisValue } from 'scenes/insights/aggregationAxisFormat'
import { insightLogic } from 'scenes/insights/insightLogic'
import { formatBreakdownLabel, getTrendResultCustomizationColorToken } from 'scenes/insights/utils'
import { formatBreakdownLabel } from 'scenes/insights/utils'
import { formatCompareLabel } from 'scenes/insights/views/InsightsTable/columns/SeriesColumn'
import { trendsDataLogic } from 'scenes/trends/trendsDataLogic'
import { IndexedTrendResult } from 'scenes/trends/types'
Expand All @@ -27,19 +26,11 @@ export function InsightLegendRow({ rowIndex, item }: InsightLegendRowProps): JSX
const { formatPropertyValueForDisplay } = useValues(propertyDefinitionsModel)

const { insightProps, highlightedSeries } = useValues(insightLogic)
const {
display,
trendsFilter,
breakdownFilter,
isSingleSeries,
hiddenLegendIndexes,
resultCustomizationBy,
resultCustomizations,
} = useValues(trendsDataLogic(insightProps))
const { display, trendsFilter, breakdownFilter, isSingleSeries, hiddenLegendIndexes, getTrendsColor } = useValues(
trendsDataLogic(insightProps)
)
const { toggleHiddenLegendIndex } = useActions(trendsDataLogic(insightProps))

const { getTheme } = useValues(dataThemeLogic)

const highlighted = shouldHighlightThisRow(rowIndex, highlightedSeries, hiddenLegendIndexes)
const highlightStyle: Record<string, any> = highlighted
? {
Expand All @@ -63,11 +54,8 @@ export function InsightLegendRow({ rowIndex, item }: InsightLegendRowProps): JSX

const isPrevious = !!item.compare && item.compare_label === 'previous'

const theme = getTheme('posthog')
const colorToken = getTrendResultCustomizationColorToken(resultCustomizationBy, resultCustomizations, theme, item)

const themeColor = theme[colorToken]
const mainColor = isPrevious ? `${themeColor}80` : themeColor
const themeColor = getTrendsColor(item)
const mainColor = (isPrevious ? `${themeColor}80` : themeColor) || undefined

return (
<div key={item.id} className="InsightLegendMenu-item p-2 flex flex-row" ref={rowRef} {...highlightStyle}>
Expand Down
34 changes: 33 additions & 1 deletion frontend/src/lib/components/SeriesGlyph.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { useValues } from 'kea'
import { getSeriesColor } from 'lib/colors'
import { alphabet, hexToRGBA, lightenDarkenColor, RGBToRGBA } from 'lib/utils'
import { useEffect, useState } from 'react'

import { themeLogic } from '~/layout/navigation-3000/themeLogic'

interface SeriesGlyphProps {
className?: string
children: React.ReactNode
children?: React.ReactNode
style?: React.CSSProperties
variant?: 'funnel-step-glyph' // Built-in styling defaults
}
Expand All @@ -20,6 +21,37 @@ export function SeriesGlyph({ className, style, children, variant }: SeriesGlyph
)
}

type ColorGlyphProps = {
color?: string | null
} & SeriesGlyphProps

export function ColorGlyph({ color, ...rest }: ColorGlyphProps): JSX.Element {
const { isDarkModeOn } = useValues(themeLogic)

const [lastValidColor, setLastValidColor] = useState<string>('#000000')

useEffect(() => {
// allow only 6-digit hex colors
// other color formats are not supported everywhere e.g. insight visualizations
if (color != null && /^#[0-9A-Fa-f]{6}$/.test(color)) {
setLastValidColor(color)
}
}, [color])

return (
<SeriesGlyph
style={{
borderColor: lastValidColor,
color: lastValidColor,
backgroundColor: isDarkModeOn
? RGBToRGBA(lightenDarkenColor(lastValidColor, -20), 0.3)
: hexToRGBA(lastValidColor, 0.2),
}}
{...rest}
/>
)
}

interface SeriesLetterProps {
className?: string
hasBreakdown: boolean
Expand Down
68 changes: 68 additions & 0 deletions frontend/src/lib/lemon-ui/colors.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,24 @@ const threeThousand = [
'primary-alt',
]

const dataColors = [
'data-color-1',
'data-color-2',
'data-color-3',
'data-color-4',
'data-color-5',
'data-color-6',
'data-color-7',
'data-color-8',
'data-color-9',
'data-color-10',
'data-color-11',
'data-color-12',
'data-color-13',
'data-color-14',
'data-color-15',
]

export function ColorPalette(): JSX.Element {
const [hover, setHover] = useState<string>()
return (
Expand Down Expand Up @@ -278,3 +296,53 @@ export function AllThreeThousandColorOptions(): JSX.Element {
/>
)
}

export function DataColors(): JSX.Element {
return (
<LemonTable
dataSource={dataColors.map((color) => ({ name: color, color }))}
columns={[
{
title: 'Class name',
key: 'name',
dataIndex: 'name',
render: function RenderName(name) {
return name
},
},
{
title: 'Light mode',
key: 'light',
dataIndex: 'color',
render: function RenderColor(color) {
return (
<div className="bg-bg-3000-light flex items-center justify-center border rounded h-16 w-16">
<div
className="border rounded h-8 w-8"
// eslint-disable-next-line react/forbid-dom-props
style={{ backgroundColor: `var(--${color})` }}
/>
</div>
)
},
},
{
title: 'Dark mode',
key: 'dark',
dataIndex: 'color',
render: function RenderColor(color) {
return (
<div className="bg-bg-3000-dark flex items-center justify-center border rounded h-16 w-16">
<div
className="border rounded h-8 w-8"
// eslint-disable-next-line react/forbid-dom-props
style={{ backgroundColor: `var(--${color})` }}
/>
</div>
)
},
},
]}
/>
)
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { LemonButton, Popover } from '@posthog/lemon-ui'
import { useValues } from 'kea'
import { SeriesGlyph } from 'lib/components/SeriesGlyph'
import { hexToRGBA, lightenDarkenColor, RGBToHex, RGBToRGBA } from 'lib/utils'
import { ColorGlyph } from 'lib/components/SeriesGlyph'
import { lightenDarkenColor, RGBToHex } from 'lib/utils'
import { useState } from 'react'
import { ColorResult, TwitterPicker } from 'react-color'

Expand Down Expand Up @@ -57,17 +57,7 @@ export const ColorPickerButton = ({
sideIcon={<></>}
className="ConditionalFormattingTab__ColorPicker"
>
<SeriesGlyph
style={{
borderColor: color,
color: color,
backgroundColor: isDarkModeOn
? RGBToRGBA(lightenDarkenColor(color, -20), 0.3)
: hexToRGBA(color, 0.5),
}}
>
<></>
</SeriesGlyph>
<ColorGlyph color={color} />
</LemonButton>
</Popover>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ import './ConditionalFormattingTab.scss'
import { IconPlusSmall, IconTrash } from '@posthog/icons'
import { LemonButton, LemonCollapse, LemonInput, LemonSelect, LemonTag } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { SeriesGlyph } from 'lib/components/SeriesGlyph'
import { hexToRGBA, lightenDarkenColor, RGBToRGBA } from 'lib/utils'
import { ColorGlyph } from 'lib/components/SeriesGlyph'

import { themeLogic } from '~/layout/navigation-3000/themeLogic'
import { ColorPickerButton } from '~/queries/nodes/DataVisualization/Components/ColorPickerButton'
import { ConditionalFormattingRule } from '~/queries/schema'

Expand All @@ -33,7 +31,6 @@ const getRuleHeader = (rule: ConditionalFormattingRule): string => {
}

export const ConditionalFormattingTab = (): JSX.Element => {
const { isDarkModeOn } = useValues(themeLogic)
const { conditionalFormattingRules, conditionalFormattingRulesPanelActiveKeys } = useValues(dataVisualizationLogic)
const { addConditionalFormattingRule, setConditionalFormattingRulesPanelActiveKeys } =
useActions(dataVisualizationLogic)
Expand All @@ -53,17 +50,7 @@ export const ConditionalFormattingTab = (): JSX.Element => {
key: rule.id,
header: (
<>
<SeriesGlyph
style={{
borderColor: rule.color,
color: rule.color,
backgroundColor: isDarkModeOn
? RGBToRGBA(lightenDarkenColor(rule.color, -20), 0.3)
: hexToRGBA(rule.color, 0.2),
}}
>
<></>
</SeriesGlyph>
<ColorGlyph color={rule.color} />
<span className="ml-2">{getRuleHeader(rule)}</span>
</>
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,8 @@ import {
import { useActions, useValues } from 'kea'
import { Form } from 'kea-forms'
import { getSeriesColor, getSeriesColorPalette } from 'lib/colors'
import { SeriesGlyph } from 'lib/components/SeriesGlyph'
import { ColorGlyph } from 'lib/components/SeriesGlyph'
import { LemonField } from 'lib/lemon-ui/LemonField'
import { hexToRGBA, lightenDarkenColor, RGBToRGBA } from 'lib/utils'

import { themeLogic } from '~/layout/navigation-3000/themeLogic'

import { AxisSeries, dataVisualizationLogic } from '../dataVisualizationLogic'
import { ColorPickerButton } from './ColorPickerButton'
Expand Down Expand Up @@ -126,7 +123,6 @@ const YSeries = ({ series, index }: { series: AxisSeries<number>; index: number
const { isSettingsOpen, canOpenSettings, activeSettingsTab } = useValues(seriesLogic)
const { setSettingsOpen, submitFormatting, submitDisplay, setSettingsTab } = useActions(seriesLogic)

const { isDarkModeOn } = useValues(themeLogic)
const seriesColor = series.settings?.display?.color ?? getSeriesColor(index)
const showSeriesColor = !showTableSettings && !selectedSeriesBreakdownColumn

Expand All @@ -135,20 +131,7 @@ const YSeries = ({ series, index }: { series: AxisSeries<number>; index: number
value: name,
label: (
<div className="items-center flex flex-1">
{showSeriesColor && (
<SeriesGlyph
style={{
borderColor: seriesColor,
color: seriesColor,
backgroundColor: isDarkModeOn
? RGBToRGBA(lightenDarkenColor(seriesColor, -20), 0.3)
: hexToRGBA(seriesColor, 0.2),
}}
className="mr-2"
>
<></>
</SeriesGlyph>
)}
{showSeriesColor && <ColorGlyph className="mr-2" color={seriesColor} />}
{series.settings?.display?.label && series.column.name === name ? series.settings.display.label : name}
<LemonTag className="ml-2" type="default">
{type.name}
Expand Down Expand Up @@ -399,24 +382,12 @@ export const SeriesBreakdownSelector = (): JSX.Element => {
}

const BreakdownSeries = ({ series, index }: { series: AxisBreakdownSeries<number>; index: number }): JSX.Element => {
const { isDarkModeOn } = useValues(themeLogic)
const seriesColor = series.settings?.display?.color ?? getSeriesColor(index)

return (
<div className="flex gap-1 mb-2">
<div className="flex gap-2">
<SeriesGlyph
style={{
borderColor: seriesColor,
color: seriesColor,
backgroundColor: isDarkModeOn
? RGBToRGBA(lightenDarkenColor(seriesColor, -20), 0.3)
: hexToRGBA(seriesColor, 0.2),
}}
className="mr-2"
>
<></>
</SeriesGlyph>
<ColorGlyph color={seriesColor} className="mr-2" />
<span>{series.name ? series.name : '[No value]'}</span>
</div>
{/* For now let's keep things simple and not allow too much configuration */}
Expand Down
Loading
Loading