diff --git a/frontend/__snapshots__/components-activitylog--team-activity--dark.png b/frontend/__snapshots__/components-activitylog--team-activity--dark.png index d7bc111b04eb0..69ffefc47369b 100644 Binary files a/frontend/__snapshots__/components-activitylog--team-activity--dark.png and b/frontend/__snapshots__/components-activitylog--team-activity--dark.png differ diff --git a/frontend/__snapshots__/components-activitylog--team-activity--light.png b/frontend/__snapshots__/components-activitylog--team-activity--light.png index 5b48342c45011..0ac59e3276446 100644 Binary files a/frontend/__snapshots__/components-activitylog--team-activity--light.png and b/frontend/__snapshots__/components-activitylog--team-activity--light.png differ diff --git a/frontend/__snapshots__/lemon-ui-colors--data-colors--dark.png b/frontend/__snapshots__/lemon-ui-colors--data-colors--dark.png new file mode 100644 index 0000000000000..e5ad4abf9efdf Binary files /dev/null and b/frontend/__snapshots__/lemon-ui-colors--data-colors--dark.png differ diff --git a/frontend/__snapshots__/lemon-ui-colors--data-colors--light.png b/frontend/__snapshots__/lemon-ui-colors--data-colors--light.png new file mode 100644 index 0000000000000..4ea608850368c Binary files /dev/null and b/frontend/__snapshots__/lemon-ui-colors--data-colors--light.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom--dark.png b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom--dark.png index 6f05da51cc8d3..df215f085e972 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom--dark.png and b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom--dark.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-project--dark.png b/frontend/__snapshots__/scenes-other-settings--settings-project--dark.png index 24b46c282634e..38b0035dfc39f 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-project--dark.png and b/frontend/__snapshots__/scenes-other-settings--settings-project--dark.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-project--light.png b/frontend/__snapshots__/scenes-other-settings--settings-project--light.png index ff07ce0325337..8b2df77bc773f 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-project--light.png and b/frontend/__snapshots__/scenes-other-settings--settings-project--light.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-project-with-replay-features--dark.png b/frontend/__snapshots__/scenes-other-settings--settings-project-with-replay-features--dark.png index c8cd83be7e7cd..50d9e0dfcd035 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-project-with-replay-features--dark.png and b/frontend/__snapshots__/scenes-other-settings--settings-project-with-replay-features--dark.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-project-with-replay-features--light.png b/frontend/__snapshots__/scenes-other-settings--settings-project-with-replay-features--light.png index e8fe754ac0735..e0635b00d6e0d 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-project-with-replay-features--light.png and b/frontend/__snapshots__/scenes-other-settings--settings-project-with-replay-features--light.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-all-options--dark.png b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-all-options--dark.png index 24b46c282634e..38b0035dfc39f 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-all-options--dark.png and b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-all-options--dark.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-all-options--light.png b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-all-options--light.png index ff07ce0325337..8b2df77bc773f 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-all-options--light.png and b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-all-options--light.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-password-only--dark.png b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-password-only--dark.png index 24b46c282634e..38b0035dfc39f 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-password-only--dark.png and b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-password-only--dark.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-password-only--light.png b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-password-only--light.png index ff07ce0325337..8b2df77bc773f 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-password-only--light.png and b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-password-only--light.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-github--dark.png b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-github--dark.png index 24b46c282634e..38b0035dfc39f 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-github--dark.png and b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-github--dark.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-github--light.png b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-github--light.png index ff07ce0325337..8b2df77bc773f 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-github--light.png and b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-github--light.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-google--dark.png b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-google--dark.png index 24b46c282634e..38b0035dfc39f 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-google--dark.png and b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-google--dark.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-google--light.png b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-google--light.png index ff07ce0325337..8b2df77bc773f 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-google--light.png and b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-google--light.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-saml--dark.png b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-saml--dark.png index 24b46c282634e..38b0035dfc39f 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-saml--dark.png and b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-saml--dark.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-saml--light.png b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-saml--light.png index ff07ce0325337..8b2df77bc773f 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-saml--light.png and b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-enforced-saml--light.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-only--dark.png b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-only--dark.png index 24b46c282634e..38b0035dfc39f 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-only--dark.png and b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-only--dark.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-only--light.png b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-only--light.png index ff07ce0325337..8b2df77bc773f 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-only--light.png and b/frontend/__snapshots__/scenes-other-settings--settings-session-timeout-sso-only--light.png differ diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index c4e1736141500..e0d0a24c8ca56 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -38,6 +38,7 @@ import { DashboardTemplateListParams, DashboardTemplateType, DashboardType, + DataColorThemeModel, DataWarehouseSavedQuery, DataWarehouseTable, DataWarehouseViewLink, @@ -913,6 +914,15 @@ class ApiRequest { public async delete(): Promise { 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 => { @@ -2456,6 +2466,18 @@ const api = { }, }, + dataColorThemes: { + async list(): Promise { + return await new ApiRequest().dataColorThemes().get() + }, + async create(data: Partial): Promise { + return await new ApiRequest().dataColorThemes().create({ data }) + }, + async update(id: DataColorThemeModel['id'], data: Partial): Promise { + return await new ApiRequest().dataColorTheme(id).update({ data }) + }, + }, + queryURL: (): string => { return new ApiRequest().query().assembleFullUrl(true) }, diff --git a/frontend/src/lib/components/InsightLegend/InsightLegendRow.tsx b/frontend/src/lib/components/InsightLegend/InsightLegendRow.tsx index 0ec3ea30f63d2..62a94e1ed813e 100644 --- a/frontend/src/lib/components/InsightLegend/InsightLegendRow.tsx +++ b/frontend/src/lib/components/InsightLegend/InsightLegendRow.tsx @@ -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' @@ -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 = highlighted ? { @@ -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 (
diff --git a/frontend/src/lib/components/SeriesGlyph.tsx b/frontend/src/lib/components/SeriesGlyph.tsx index 1ebc8d30b3b1d..81ac6d3aec6df 100644 --- a/frontend/src/lib/components/SeriesGlyph.tsx +++ b/frontend/src/lib/components/SeriesGlyph.tsx @@ -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 } @@ -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('#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 ( + + ) +} + interface SeriesLetterProps { className?: string hasBreakdown: boolean diff --git a/frontend/src/lib/lemon-ui/colors.stories.tsx b/frontend/src/lib/lemon-ui/colors.stories.tsx index f2e87c73528af..1a578ea7c0029 100644 --- a/frontend/src/lib/lemon-ui/colors.stories.tsx +++ b/frontend/src/lib/lemon-ui/colors.stories.tsx @@ -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() return ( @@ -278,3 +296,53 @@ export function AllThreeThousandColorOptions(): JSX.Element { /> ) } + +export function DataColors(): JSX.Element { + return ( + ({ 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 ( +
+
+
+ ) + }, + }, + { + title: 'Dark mode', + key: 'dark', + dataIndex: 'color', + render: function RenderColor(color) { + return ( +
+
+
+ ) + }, + }, + ]} + /> + ) +} diff --git a/frontend/src/queries/nodes/DataVisualization/Components/ColorPickerButton.tsx b/frontend/src/queries/nodes/DataVisualization/Components/ColorPickerButton.tsx index a9fdac3aedf91..107f9d426d83e 100644 --- a/frontend/src/queries/nodes/DataVisualization/Components/ColorPickerButton.tsx +++ b/frontend/src/queries/nodes/DataVisualization/Components/ColorPickerButton.tsx @@ -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' @@ -57,17 +57,7 @@ export const ColorPickerButton = ({ sideIcon={<>} className="ConditionalFormattingTab__ColorPicker" > - - <> - + ) diff --git a/frontend/src/queries/nodes/DataVisualization/Components/ConditionalFormatting/ConditionalFormattingTab.tsx b/frontend/src/queries/nodes/DataVisualization/Components/ConditionalFormatting/ConditionalFormattingTab.tsx index 30cd92628f817..5f94978e5afb1 100644 --- a/frontend/src/queries/nodes/DataVisualization/Components/ConditionalFormatting/ConditionalFormattingTab.tsx +++ b/frontend/src/queries/nodes/DataVisualization/Components/ConditionalFormatting/ConditionalFormattingTab.tsx @@ -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' @@ -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) @@ -53,17 +50,7 @@ export const ConditionalFormattingTab = (): JSX.Element => { key: rule.id, header: ( <> - - <> - + {getRuleHeader(rule)} ), diff --git a/frontend/src/queries/nodes/DataVisualization/Components/SeriesTab.tsx b/frontend/src/queries/nodes/DataVisualization/Components/SeriesTab.tsx index d500bdf9e2f00..4c1db2454dd1f 100644 --- a/frontend/src/queries/nodes/DataVisualization/Components/SeriesTab.tsx +++ b/frontend/src/queries/nodes/DataVisualization/Components/SeriesTab.tsx @@ -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' @@ -126,7 +123,6 @@ const YSeries = ({ series, index }: { series: AxisSeries; 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 @@ -135,20 +131,7 @@ const YSeries = ({ series, index }: { series: AxisSeries; index: number value: name, label: (
- {showSeriesColor && ( - - <> - - )} + {showSeriesColor && } {series.settings?.display?.label && series.column.name === name ? series.settings.display.label : name} {type.name} @@ -399,24 +382,12 @@ export const SeriesBreakdownSelector = (): JSX.Element => { } const BreakdownSeries = ({ series, index }: { series: AxisBreakdownSeries; index: number }): JSX.Element => { - const { isDarkModeOn } = useValues(themeLogic) const seriesColor = series.settings?.display?.color ?? getSeriesColor(index) return (
- - <> - + {series.name ? series.name : '[No value]'}
{/* For now let's keep things simple and not allow too much configuration */} diff --git a/frontend/src/queries/nodes/InsightViz/InsightVizDisplay.tsx b/frontend/src/queries/nodes/InsightViz/InsightVizDisplay.tsx index fcbed5fef7f04..6f6531a3669ac 100644 --- a/frontend/src/queries/nodes/InsightViz/InsightVizDisplay.tsx +++ b/frontend/src/queries/nodes/InsightViz/InsightVizDisplay.tsx @@ -56,13 +56,13 @@ export function InsightVizDisplay({ context?: QueryContext embedded: boolean inSharedMode?: boolean -}): JSX.Element { +}): JSX.Element | null { const { insightProps, canEditInsight } = useValues(insightLogic) const { activeView } = useValues(insightNavLogic(insightProps)) const { hasFunnelResults } = useValues(funnelDataLogic(insightProps)) - const { isFunnelWithEnoughSteps, validationError } = useValues(insightVizDataLogic(insightProps)) + const { isFunnelWithEnoughSteps, validationError, theme } = useValues(insightVizDataLogic(insightProps)) const { isFunnels, isPaths, @@ -219,6 +219,10 @@ export function InsightVizDisplay({ const showComputationMetadata = !disableLastComputation || !!samplingFactor + if (!theme) { + return null + } + return ( <> {/* These are filters that are reused between insight features. They each have generic logic that updates the url */} @@ -266,15 +270,13 @@ export function InsightVizDisplay({
) : ( - <> - {renderActiveView()} - - + <>{renderActiveView()} )}
)}
+ {renderTable()} {!disableCorrelationTable && activeView === InsightType.FUNNELS && } diff --git a/frontend/src/queries/nodes/InsightViz/ResultCustomizationsModal.tsx b/frontend/src/queries/nodes/InsightViz/ResultCustomizationsModal.tsx index 4b9413da90022..8fcccb030ef5d 100644 --- a/frontend/src/queries/nodes/InsightViz/ResultCustomizationsModal.tsx +++ b/frontend/src/queries/nodes/InsightViz/ResultCustomizationsModal.tsx @@ -4,14 +4,12 @@ import { LemonButton, LemonButtonProps, LemonModal } from '@posthog/lemon-ui' import { useActions, useValues } from 'kea' import { DataColorToken } from 'lib/colors' import { InsightLabel } from 'lib/components/InsightLabel' -import { SeriesGlyph } from 'lib/components/SeriesGlyph' -import { hexToRGBA, lightenDarkenColor, RGBToRGBA } from 'lib/utils' +import { ColorGlyph } from 'lib/components/SeriesGlyph' import { dataThemeLogic } from 'scenes/dataThemeLogic' import { insightLogic } from 'scenes/insights/insightLogic' import { insightVizDataLogic } from 'scenes/insights/insightVizDataLogic' import { IndexedTrendResult } from 'scenes/trends/types' -import { themeLogic } from '~/layout/navigation-3000/themeLogic' import { ResultCustomizationBy } from '~/queries/schema' import { FlattenedFunnelStepByBreakdown } from '~/types' @@ -26,7 +24,7 @@ export function ResultCustomizationsModal(): JSX.Element | null { ) const { closeModal, setColorToken, save } = useActions(resultCustomizationsModalLogic(insightProps)) - const { isTrends, isFunnels } = useValues(insightVizDataLogic) + const { isTrends, isFunnels, querySource } = useValues(insightVizDataLogic) const { getTheme } = useValues(dataThemeLogic) @@ -34,7 +32,7 @@ export function ResultCustomizationsModal(): JSX.Element | null { return null } - const theme = getTheme('posthog') + const theme = getTheme(querySource?.dataColorTheme) return ( Color
- {Object.keys(theme).map((key) => ( + {Object.keys(theme || {}).map((key) => ( - - <> - + ) } diff --git a/frontend/src/queries/nodes/InsightViz/resultCustomizationsModalLogic.ts b/frontend/src/queries/nodes/InsightViz/resultCustomizationsModalLogic.ts index cee00f66e7cdb..62ad1795aa438 100644 --- a/frontend/src/queries/nodes/InsightViz/resultCustomizationsModalLogic.ts +++ b/frontend/src/queries/nodes/InsightViz/resultCustomizationsModalLogic.ts @@ -2,17 +2,11 @@ import { actions, connect, kea, key, listeners, path, props, reducers, selectors import { DataColorToken } from 'lib/colors' import { FEATURE_FLAGS } from 'lib/constants' import { featureFlagLogic } from 'lib/logic/featureFlagLogic' -import { dataThemeLogic } from 'scenes/dataThemeLogic' import { funnelDataLogic } from 'scenes/funnels/funnelDataLogic' import { RESULT_CUSTOMIZATION_DEFAULT } from 'scenes/insights/EditorFilters/ResultCustomizationByPicker' import { insightVizDataLogic } from 'scenes/insights/insightVizDataLogic' import { keyForInsightLogicProps } from 'scenes/insights/sharedUtils' -import { - getFunnelDatasetKey, - getFunnelResultCustomizationColorToken, - getTrendResultCustomizationColorToken, - getTrendResultCustomizationKey, -} from 'scenes/insights/utils' +import { getFunnelDatasetKey, getTrendResultCustomizationKey } from 'scenes/insights/utils' import { trendsDataLogic } from 'scenes/trends/trendsDataLogic' import { IndexedTrendResult } from 'scenes/trends/types' @@ -31,11 +25,13 @@ export const resultCustomizationsModalLogic = kea localColorToken || colorTokenFromQuery, ], colorTokenFromQuery: [ - (s) => [ - s.isTrends, - s.isFunnels, - s.resultCustomizationBy, - s.trendsResultCustomizations, - s.funnelsResultCustomizations, - s.getTheme, - s.dataset, - ], - ( - isTrends, - isFunnels, - resultCustomizationBy, - trendsResultCustomizations, - funnelsResultCustomizations, - getTheme, - dataset - ): DataColorToken | null => { + (s) => [s.isTrends, s.isFunnels, s.getTrendsColor, s.getFunnelsColor, s.dataset], + (isTrends, isFunnels, getTrendsColor, getFunnelsColor, dataset): DataColorToken | null => { if (!dataset) { return null } - const theme = getTheme('posthog') - if (isTrends) { - return getTrendResultCustomizationColorToken( - resultCustomizationBy, - trendsResultCustomizations, - theme, - dataset as IndexedTrendResult - ) + return getTrendsColor(dataset as IndexedTrendResult) } else if (isFunnels) { - return getFunnelResultCustomizationColorToken( - funnelsResultCustomizations, - theme, - dataset as FlattenedFunnelStepByBreakdown - ) + return getFunnelsColor(dataset as FlattenedFunnelStepByBreakdown) } return null diff --git a/frontend/src/queries/schema.json b/frontend/src/queries/schema.json index f99509e1b9d74..10fc163512233 100644 --- a/frontend/src/queries/schema.json +++ b/frontend/src/queries/schema.json @@ -6978,6 +6978,10 @@ "$ref": "#/definitions/BreakdownFilter", "description": "Breakdown of the events and actions" }, + "dataColorTheme": { + "description": "Colors used in the insight's visualization", + "type": ["number", "null"] + }, "dateRange": { "$ref": "#/definitions/InsightDateRange", "description": "Date range for the query" @@ -8064,6 +8068,10 @@ "description": "Groups aggregation", "type": "integer" }, + "dataColorTheme": { + "description": "Colors used in the insight's visualization", + "type": ["number", "null"] + }, "dateRange": { "$ref": "#/definitions/InsightDateRange", "description": "Date range for the query" @@ -8114,6 +8122,10 @@ "description": "Groups aggregation", "type": "integer" }, + "dataColorTheme": { + "description": "Colors used in the insight's visualization", + "type": ["number", "null"] + }, "dateRange": { "$ref": "#/definitions/InsightDateRange", "description": "Date range for the query" @@ -8164,6 +8176,10 @@ "description": "Groups aggregation", "type": "integer" }, + "dataColorTheme": { + "description": "Colors used in the insight's visualization", + "type": ["number", "null"] + }, "dateRange": { "$ref": "#/definitions/InsightDateRange", "description": "Date range for the query" @@ -8214,6 +8230,10 @@ "description": "Groups aggregation", "type": "integer" }, + "dataColorTheme": { + "description": "Colors used in the insight's visualization", + "type": ["number", "null"] + }, "dateRange": { "$ref": "#/definitions/InsightDateRange", "description": "Date range for the query" @@ -8264,6 +8284,10 @@ "description": "Groups aggregation", "type": "integer" }, + "dataColorTheme": { + "description": "Colors used in the insight's visualization", + "type": ["number", "null"] + }, "dateRange": { "$ref": "#/definitions/InsightDateRange", "description": "Date range for the query" @@ -8367,6 +8391,10 @@ "description": "Groups aggregation", "type": "integer" }, + "dataColorTheme": { + "description": "Colors used in the insight's visualization", + "type": ["number", "null"] + }, "dateRange": { "$ref": "#/definitions/InsightDateRange", "description": "Date range for the query" @@ -8770,6 +8798,10 @@ "description": "Groups aggregation", "type": "integer" }, + "dataColorTheme": { + "description": "Colors used in the insight's visualization", + "type": ["number", "null"] + }, "dateRange": { "$ref": "#/definitions/InsightDateRange", "description": "Date range for the query" @@ -11643,6 +11675,10 @@ "description": "Groups aggregation", "type": "integer" }, + "dataColorTheme": { + "description": "Colors used in the insight's visualization", + "type": ["number", "null"] + }, "dateRange": { "$ref": "#/definitions/InsightDateRange", "description": "Date range for the query" @@ -12295,6 +12331,10 @@ "$ref": "#/definitions/CompareFilter", "description": "Compare to date range" }, + "dataColorTheme": { + "description": "Colors used in the insight's visualization", + "type": ["number", "null"] + }, "dateRange": { "$ref": "#/definitions/InsightDateRange", "description": "Date range for the query" @@ -12853,6 +12893,10 @@ "$ref": "#/definitions/CompareFilter", "description": "Compare to date range" }, + "dataColorTheme": { + "description": "Colors used in the insight's visualization", + "type": ["number", "null"] + }, "dateRange": { "$ref": "#/definitions/InsightDateRange", "description": "Date range for the query" diff --git a/frontend/src/queries/schema.ts b/frontend/src/queries/schema.ts index b3efd96da6f31..b84255e3bd165 100644 --- a/frontend/src/queries/schema.ts +++ b/frontend/src/queries/schema.ts @@ -837,6 +837,8 @@ export interface InsightsQueryBase> ex aggregation_group_type_index?: integer /** Sampling rate */ samplingFactor?: number | null + /** Colors used in the insight's visualization */ + dataColorTheme?: number | null /** Modifiers used when performing the query */ modifiers?: HogQLQueryModifiers } diff --git a/frontend/src/scenes/dataThemeLogic.test.ts b/frontend/src/scenes/dataThemeLogic.test.ts new file mode 100644 index 0000000000000..822da57237d13 --- /dev/null +++ b/frontend/src/scenes/dataThemeLogic.test.ts @@ -0,0 +1,22 @@ +import { DataColorThemeModel } from '~/types' + +import { convertApiTheme } from './dataThemeLogic' + +describe('convertApiTheme', () => { + it('converts api theme', () => { + const apiTheme: DataColorThemeModel = { + id: 1, + name: 'Default theme', + colors: ['#ff0000', '#00ff00', '#0000ff'], + is_global: true, + } + + const theme = convertApiTheme(apiTheme) + + expect(theme).toEqual({ + 'preset-1': '#ff0000', + 'preset-2': '#00ff00', + 'preset-3': '#0000ff', + }) + }) +}) diff --git a/frontend/src/scenes/dataThemeLogic.ts b/frontend/src/scenes/dataThemeLogic.ts index e7d79abe95275..6b4f47aaef520 100644 --- a/frontend/src/scenes/dataThemeLogic.ts +++ b/frontend/src/scenes/dataThemeLogic.ts @@ -1,54 +1,67 @@ -import { kea, path, selectors } from 'kea' -import { DataColorTheme, getColorVar } from 'lib/colors' +import { afterMount, connect, kea, path, selectors } from 'kea' +import { loaders } from 'kea-loaders' +import api from 'lib/api' +import { DataColorTheme } from 'lib/colors' -import type { dataThemeLogicType } from './dataThemeLogicType' +import { DataColorThemeModel } from '~/types' -const POSTHOG_THEME: DataColorTheme = { - 'preset-1': getColorVar('data-color-1'), - 'preset-2': getColorVar('data-color-2'), - 'preset-3': getColorVar('data-color-3'), - 'preset-4': getColorVar('data-color-4'), - 'preset-5': getColorVar('data-color-5'), - 'preset-6': getColorVar('data-color-6'), - 'preset-7': getColorVar('data-color-7'), - 'preset-8': getColorVar('data-color-8'), - 'preset-9': getColorVar('data-color-9'), - 'preset-10': getColorVar('data-color-10'), - 'preset-11': getColorVar('data-color-11'), - 'preset-12': getColorVar('data-color-12'), - 'preset-13': getColorVar('data-color-13'), - 'preset-14': getColorVar('data-color-14'), - 'preset-15': getColorVar('data-color-15'), -} +import type { dataThemeLogicType } from './dataThemeLogicType' +import { teamLogic } from './teamLogic' -const D3_SCHEME_CATEGORY_10: DataColorTheme = { - 'preset-1': '#1f77b4', - 'preset-2': '#ff7f0e', - 'preset-3': '#2ca02c', - 'preset-4': '#d62728', - 'preset-5': '#9467bd', - 'preset-6': '#8c564b', - 'preset-7': '#e377c2', - 'preset-8': '#7f7f7f', - 'preset-9': '#bcbd22', - 'preset-10': '#17becf', +/** Returns a data color theme from the backend side theme model. */ +export function convertApiTheme(apiTheme: DataColorThemeModel): DataColorTheme { + return apiTheme.colors.reduce((theme, color, index) => { + theme[`preset-${index + 1}`] = color + return theme + }, {}) } export const dataThemeLogic = kea([ path(['scenes', 'dataThemeLogic']), - selectors({ + connect({ values: [teamLogic, ['currentTeam']] }), + loaders({ themes: [ - () => [], - () => ({ - posthog: POSTHOG_THEME, - d3_category_10: D3_SCHEME_CATEGORY_10, - }), + null as null | DataColorThemeModel[], + { + loadThemes: async () => await api.dataColorThemes.list(), + }, + ], + }), + selectors({ + defaultTheme: [ + (s) => [s.currentTeam, s.themes], + (currentTeam, themes) => { + if (!currentTeam || !themes) { + return null + } + + const environmentTheme = themes.find((theme) => theme.id === currentTeam.default_data_theme) + return environmentTheme || themes.find((theme) => theme.is_global) + }, ], getTheme: [ - (s) => [s.themes], - (themes) => - (theme: string): DataColorTheme => - themes[theme], + (s) => [s.themes, s.defaultTheme], + (themes, defaultTheme) => + (themeId: string | number | null | undefined): DataColorTheme | null => { + let customTheme + + if (Number.isInteger(themeId) && themes != null) { + customTheme = themes.find((theme) => theme.id === themeId) + } + + if (customTheme) { + return convertApiTheme(customTheme) + } + + if (defaultTheme) { + return convertApiTheme(defaultTheme) + } + + return null + }, ], }), + afterMount(({ actions }) => { + actions.loadThemes() + }), ]) diff --git a/frontend/src/scenes/funnels/FunnelBarHorizontal/Bar.tsx b/frontend/src/scenes/funnels/FunnelBarHorizontal/Bar.tsx index 8142842e87e12..fd9160af43997 100644 --- a/frontend/src/scenes/funnels/FunnelBarHorizontal/Bar.tsx +++ b/frontend/src/scenes/funnels/FunnelBarHorizontal/Bar.tsx @@ -2,9 +2,7 @@ import { LemonDropdown } from '@posthog/lemon-ui' import { useValues } from 'kea' import { capitalizeFirstLetter, percentage } from 'lib/utils' import { useEffect, useRef, useState } from 'react' -import { dataThemeLogic } from 'scenes/dataThemeLogic' import { insightLogic } from 'scenes/insights/insightLogic' -import { getFunnelResultCustomizationColorToken } from 'scenes/insights/utils' import { Noun } from '~/models/groupsModel' import { BreakdownFilter } from '~/queries/schema' @@ -48,8 +46,7 @@ export function Bar({ wrapperWidth, }: BarProps): JSX.Element | null { const { insightProps } = useValues(insightLogic) - const { resultCustomizations } = useValues(funnelDataLogic(insightProps)) - const { getTheme } = useValues(dataThemeLogic) + const { getFunnelsColor } = useValues(funnelDataLogic(insightProps)) const barRef = useRef(null) const labelRef = useRef(null) @@ -96,14 +93,6 @@ export function Bar({ return null } - const theme = getTheme('posthog') - const colorToken = getFunnelResultCustomizationColorToken( - resultCustomizations, - theme, - step, - insightProps.cachedInsight?.disable_baseline - ) - return ( { if (!disabled && onBarClick) { diff --git a/frontend/src/scenes/funnels/FunnelBarVertical/StepBar.tsx b/frontend/src/scenes/funnels/FunnelBarVertical/StepBar.tsx index 872bfdd59ec10..f5224fcf54318 100644 --- a/frontend/src/scenes/funnels/FunnelBarVertical/StepBar.tsx +++ b/frontend/src/scenes/funnels/FunnelBarVertical/StepBar.tsx @@ -2,9 +2,7 @@ import clsx from 'clsx' import { useActions, useValues } from 'kea' import { percentage } from 'lib/utils' import { useRef } from 'react' -import { dataThemeLogic } from 'scenes/dataThemeLogic' import { insightLogic } from 'scenes/insights/insightLogic' -import { getFunnelResultCustomizationColorToken } from 'scenes/insights/utils' import { FunnelStepWithConversionMetrics } from '~/types' @@ -24,28 +22,19 @@ interface StepBarCSSProperties extends React.CSSProperties { } export function StepBar({ step, stepIndex, series, showPersonsModal }: StepBarProps): JSX.Element { const { insightProps } = useValues(insightLogic) - const { disableFunnelBreakdownBaseline, resultCustomizations } = useValues(funnelDataLogic(insightProps)) + const { getFunnelsColor } = useValues(funnelDataLogic(insightProps)) const { showTooltip, hideTooltip } = useActions(funnelTooltipLogic(insightProps)) const { openPersonsModalForSeries } = useActions(funnelPersonsModalLogic(insightProps)) - const { getTheme } = useValues(dataThemeLogic) const ref = useRef(null) - const theme = getTheme('posthog') - const colorToken = getFunnelResultCustomizationColorToken( - resultCustomizations, - theme, - series, - disableFunnelBreakdownBaseline - ) - return (
([ 'interval', 'insightData', 'insightDataError', + 'theme', ], groupsModel, ['aggregationLabel'], @@ -84,7 +86,7 @@ export const funnelDataLogic = kea([ ], }), - selectors(() => ({ + selectors(({ props }) => ({ querySource: [ (s) => [s.vizQuerySource], (vizQuerySource) => (isFunnelsQuery(vizQuerySource) ? vizQuerySource : null), @@ -407,6 +409,21 @@ export const funnelDataLogic = kea([ (steps) => Array.isArray(steps) ? steps.map((step, index) => ({ ...step, seriesIndex: index, id: index })) : [], ], + + getFunnelsColor: [ + (s) => [s.resultCustomizations, s.theme], + (resultCustomizations, theme) => { + return (dataset) => { + const colorToken = getFunnelResultCustomizationColorToken( + resultCustomizations, + theme, + dataset, + props?.cachedInsight?.disable_baseline + ) + return theme?.[colorToken] || null + } + }, + ], })), listeners(({ actions, values }) => ({ diff --git a/frontend/src/scenes/insights/insightVizDataLogic.ts b/frontend/src/scenes/insights/insightVizDataLogic.ts index d361656451b90..559142541bca3 100644 --- a/frontend/src/scenes/insights/insightVizDataLogic.ts +++ b/frontend/src/scenes/insights/insightVizDataLogic.ts @@ -11,6 +11,7 @@ import { dayjs } from 'lib/dayjs' import { dateMapping, is12HoursOrLess, isLessThan2Days } from 'lib/utils' import posthog from 'posthog-js' import { databaseTableListLogic } from 'scenes/data-management/database/databaseTableListLogic' +import { dataThemeLogic } from 'scenes/dataThemeLogic' import { insightDataLogic } from 'scenes/insights/insightDataLogic' import { keyForInsightLogicProps } from 'scenes/insights/sharedUtils' import { sceneLogic } from 'scenes/sceneLogic' @@ -87,6 +88,8 @@ export const insightVizDataLogic = kea([ ['filterTestAccountsDefault'], databaseTableListLogic, ['dataWarehouseTablesMap'], + dataThemeLogic, + ['getTheme'], ], actions: [insightDataLogic, ['setQuery', 'setInsightData', 'loadData', 'loadDataSuccess', 'loadDataFailure']], })), @@ -381,6 +384,8 @@ export const insightVizDataLogic = kea([ (s) => [s.querySource, actionsModel.selectors.actions], (querySource, actions) => (querySource ? getAllEventNames(querySource, actions) : []), ], + + theme: [(s) => [s.getTheme, s.querySource], (getTheme, querySource) => getTheme(querySource?.dataColorTheme)], }), listeners(({ actions, values, props }) => ({ diff --git a/frontend/src/scenes/insights/utils.tsx b/frontend/src/scenes/insights/utils.tsx index d1fcc6a5c4424..d451941c6e71f 100644 --- a/frontend/src/scenes/insights/utils.tsx +++ b/frontend/src/scenes/insights/utils.tsx @@ -527,7 +527,7 @@ export function getTrendResultCustomizationColorToken( | Record | null | undefined, - theme: DataColorTheme, + theme: DataColorTheme | null, dataset: IndexedTrendResult ): DataColorToken { const resultCustomization = getTrendResultCustomization(resultCustomizationBy, dataset, resultCustomizations) @@ -536,7 +536,7 @@ export function getTrendResultCustomizationColorToken( // by the position in the dataset. colors repeat after all options // have been exhausted. const datasetPosition = getTrendDatasetPosition(dataset) - const tokenIndex = (datasetPosition % Object.keys(theme).length) + 1 + const tokenIndex = (datasetPosition % Object.keys(theme || {}).length) + 1 return resultCustomization && resultCustomization.color ? resultCustomization.color @@ -545,14 +545,14 @@ export function getTrendResultCustomizationColorToken( export function getFunnelResultCustomizationColorToken( resultCustomizations: Record | null | undefined, - theme: DataColorTheme, + theme: DataColorTheme | null | undefined, dataset: FlattenedFunnelStepByBreakdown | FunnelStepWithConversionMetrics, disableFunnelBreakdownBaseline?: boolean ): DataColorToken { const resultCustomization = getFunnelResultCustomization(dataset, resultCustomizations) const datasetPosition = getFunnelDatasetPosition(dataset, disableFunnelBreakdownBaseline) - const tokenIndex = (datasetPosition % Object.keys(theme).length) + 1 + const tokenIndex = (datasetPosition % Object.keys(theme || {}).length) + 1 return resultCustomization && resultCustomization.color ? resultCustomization.color diff --git a/frontend/src/scenes/insights/utils/queryUtils.ts b/frontend/src/scenes/insights/utils/queryUtils.ts index bb290a28f1325..b6fd803010e5a 100644 --- a/frontend/src/scenes/insights/utils/queryUtils.ts +++ b/frontend/src/scenes/insights/utils/queryUtils.ts @@ -150,6 +150,8 @@ const cleanInsightQuery = (query: InsightQueryNode, opts?: CompareQueryOpts): In resultCustomizationBy: undefined, } + cleanedQuery.dataColorTheme = undefined + if (isInsightQueryWithSeries(cleanedQuery)) { cleanedQuery.series = cleanedQuery.series.map((entity) => { const { custom_name, ...cleanedEntity } = entity diff --git a/frontend/src/scenes/insights/views/Funnels/FunnelStepsTable.tsx b/frontend/src/scenes/insights/views/Funnels/FunnelStepsTable.tsx index 721bf37c90f53..779fb9b412e8a 100644 --- a/frontend/src/scenes/insights/views/Funnels/FunnelStepsTable.tsx +++ b/frontend/src/scenes/insights/views/Funnels/FunnelStepsTable.tsx @@ -7,14 +7,13 @@ import { LemonRow } from 'lib/lemon-ui/LemonRow' import { LemonTable, LemonTableColumn, LemonTableColumnGroup } from 'lib/lemon-ui/LemonTable' import { Lettermark, LettermarkColor } from 'lib/lemon-ui/Lettermark' import { humanFriendlyDuration, humanFriendlyNumber, percentage } from 'lib/utils' -import { dataThemeLogic } from 'scenes/dataThemeLogic' import { funnelDataLogic } from 'scenes/funnels/funnelDataLogic' import { funnelPersonsModalLogic } from 'scenes/funnels/funnelPersonsModalLogic' import { getVisibilityKey } from 'scenes/funnels/funnelUtils' import { ValueInspectorButton } from 'scenes/funnels/ValueInspectorButton' import { insightLogic } from 'scenes/insights/insightLogic' import { insightVizDataLogic } from 'scenes/insights/insightVizDataLogic' -import { formatBreakdownLabel, getFunnelResultCustomizationColorToken } from 'scenes/insights/utils' +import { formatBreakdownLabel } from 'scenes/insights/utils' import { cohortsModel } from '~/models/cohortsModel' import { propertyDefinitionsModel } from '~/models/propertyDefinitionsModel' @@ -26,7 +25,7 @@ import { getActionFilterFromFunnelStep, getSignificanceFromBreakdownStep } from export function FunnelStepsTable(): JSX.Element | null { const { insightProps, insightLoading } = useValues(insightLogic) const { breakdownFilter } = useValues(insightVizDataLogic(insightProps)) - const { steps, flattenedBreakdowns, hiddenLegendBreakdowns, resultCustomizations } = useValues( + const { steps, flattenedBreakdowns, hiddenLegendBreakdowns, getFunnelsColor } = useValues( funnelDataLogic(insightProps) ) const { setHiddenLegendBreakdowns, toggleLegendBreakdownVisibility } = useActions(funnelDataLogic(insightProps)) @@ -34,7 +33,6 @@ export function FunnelStepsTable(): JSX.Element | null { const { openPersonsModalForSeries } = useActions(funnelPersonsModalLogic(insightProps)) const { hasInsightColors } = useValues(resultCustomizationsModalLogic(insightProps)) const { openModal } = useActions(resultCustomizationsModalLogic(insightProps)) - const { getTheme } = useValues(dataThemeLogic) const isOnlySeries = flattenedBreakdowns.length <= 1 @@ -307,8 +305,6 @@ export function FunnelStepsTable(): JSX.Element | null { })), ] as LemonTableColumnGroup[] - const theme = getTheme('posthog') - return ( (record.significant ? 'highlighted' : null)} - rowRibbonColor={(series) => { - const colorToken = getFunnelResultCustomizationColorToken(resultCustomizations, theme, series) - return theme[colorToken] - }} + rowRibbonColor={getFunnelsColor} firstColumnSticky /> ) diff --git a/frontend/src/scenes/insights/views/InsightsTable/InsightsTable.tsx b/frontend/src/scenes/insights/views/InsightsTable/InsightsTable.tsx index 50b9eff1a832a..b9ad89d921679 100644 --- a/frontend/src/scenes/insights/views/InsightsTable/InsightsTable.tsx +++ b/frontend/src/scenes/insights/views/InsightsTable/InsightsTable.tsx @@ -3,10 +3,9 @@ import './InsightsTable.scss' import { useActions, useValues } from 'kea' import { LemonTable, LemonTableColumn } from 'lib/lemon-ui/LemonTable' import { compare as compareFn } from 'natural-orderby' -import { dataThemeLogic } from 'scenes/dataThemeLogic' import { insightLogic } from 'scenes/insights/insightLogic' import { insightSceneLogic } from 'scenes/insights/insightSceneLogic' -import { formatBreakdownLabel, getTrendResultCustomizationColorToken } from 'scenes/insights/utils' +import { formatBreakdownLabel } from 'scenes/insights/utils' import { trendsDataLogic } from 'scenes/trends/trendsDataLogic' import { IndexedTrendResult } from 'scenes/trends/types' @@ -76,15 +75,12 @@ export function InsightsTable({ trendsFilter, isSingleSeries, hiddenLegendIndexes, - resultCustomizations, - resultCustomizationBy, + getTrendsColor, } = useValues(trendsDataLogic(insightProps)) const { toggleHiddenLegendIndex, updateHiddenLegendIndexes } = useActions(trendsDataLogic(insightProps)) const { aggregation, allowAggregation } = useValues(insightsTableDataLogic(insightProps)) const { setAggregationType } = useActions(insightsTableDataLogic(insightProps)) - const { getTheme } = useValues(dataThemeLogic) - const handleSeriesEditClick = (item: IndexedTrendResult): void => { const entityFilter = entityFilterLogic.findMounted({ typeKey: filterKey, @@ -261,8 +257,6 @@ export function InsightsTable({ columns.push(...valueColumns) } - const theme = getTheme('posthog') - return ( { const isPrevious = !!item.compare && item.compare_label === 'previous' - const colorToken = getTrendResultCustomizationColorToken( - resultCustomizationBy, - resultCustomizations, - theme, - item - ) - - const themeColor = theme[colorToken] + const themeColor = getTrendsColor(item) const mainColor = isPrevious ? `${themeColor}80` : themeColor return mainColor diff --git a/frontend/src/scenes/insights/views/LineGraph/LineGraph.tsx b/frontend/src/scenes/insights/views/LineGraph/LineGraph.tsx index d4f117a91f255..f3b909404723a 100644 --- a/frontend/src/scenes/insights/views/LineGraph/LineGraph.tsx +++ b/frontend/src/scenes/insights/views/LineGraph/LineGraph.tsx @@ -29,13 +29,11 @@ import { SeriesLetter } from 'lib/components/SeriesGlyph' import { useResizeObserver } from 'lib/hooks/useResizeObserver' import { useEffect, useRef, useState } from 'react' import { createRoot, Root } from 'react-dom/client' -import { dataThemeLogic } from 'scenes/dataThemeLogic' import { formatAggregationAxisValue, formatPercentStackAxisValue } from 'scenes/insights/aggregationAxisFormat' import { insightLogic } from 'scenes/insights/insightLogic' import { InsightTooltip } from 'scenes/insights/InsightTooltip/InsightTooltip' import { TooltipConfig } from 'scenes/insights/InsightTooltip/insightTooltipUtils' import { insightVizDataLogic } from 'scenes/insights/insightVizDataLogic' -import { getTrendResultCustomizationColorToken } from 'scenes/insights/utils' import { PieChart } from 'scenes/insights/views/LineGraph/PieChart' import { createTooltipData } from 'scenes/insights/views/LineGraph/tooltip-data' import { trendsDataLogic } from 'scenes/trends/trendsDataLogic' @@ -288,11 +286,10 @@ export function LineGraph_({ const { aggregationLabel } = useValues(groupsModel) const { isDarkModeOn } = useValues(themeLogic) - const { getTheme } = useValues(dataThemeLogic) const { insightProps, insight } = useValues(insightLogic) const { timezone, isTrends, breakdownFilter } = useValues(insightVizDataLogic(insightProps)) - const { resultCustomizations, resultCustomizationBy } = useValues(trendsDataLogic(insightProps)) + const { theme, getTrendsColor } = useValues(trendsDataLogic(insightProps)) const canvasRef = useRef(null) const [myLineChart, setMyLineChart] = useState>() @@ -324,19 +321,11 @@ export function LineGraph_({ function processDataset(dataset: ChartDataset): ChartDataset { const isPrevious = !!dataset.compare && dataset.compare_label === 'previous' - const theme = getTheme('posthog') - const colorToken = getTrendResultCustomizationColorToken( - resultCustomizationBy, - resultCustomizations, - theme, - dataset - ) - const themeColor = dataset?.status ? getBarColorFromStatus(dataset.status) : isHorizontal ? dataset.backgroundColor - : theme[colorToken] + : getTrendsColor(dataset) const mainColor = isPrevious ? `${themeColor}80` : themeColor const hoverColor = dataset?.status ? getBarColorFromStatus(dataset.status, true) : mainColor @@ -823,6 +812,7 @@ export function LineGraph_({ showValuesOnSeries, showPercentStackView, alertLines, + theme, ]) return ( diff --git a/frontend/src/scenes/settings/SettingsMap.tsx b/frontend/src/scenes/settings/SettingsMap.tsx index 5e272bb12d82a..8c69f35b73c5d 100644 --- a/frontend/src/scenes/settings/SettingsMap.tsx +++ b/frontend/src/scenes/settings/SettingsMap.tsx @@ -14,6 +14,7 @@ import { } from './environment/AutocaptureSettings' import { CorrelationConfig } from './environment/CorrelationConfig' import { DataAttributes } from './environment/DataAttributes' +import { DataColorThemes } from './environment/DataColorThemes' import { GroupAnalyticsConfig } from './environment/GroupAnalyticsConfig' import { HeatmapsSettings } from './environment/HeatmapsSettings' import { IPAllowListInfo } from './environment/IPAllowListInfo' @@ -156,6 +157,12 @@ export const SETTINGS_MAP: SettingSection[] = [ title: 'Filter out internal and test users', component: , }, + { + id: 'data-theme', + title: 'Data colors', + component: , + flag: 'INSIGHT_COLORS', + }, { id: 'persons-on-events', title: 'Person properties mode', diff --git a/frontend/src/scenes/settings/environment/DataColorThemeModal.tsx b/frontend/src/scenes/settings/environment/DataColorThemeModal.tsx new file mode 100644 index 0000000000000..16a651c621f71 --- /dev/null +++ b/frontend/src/scenes/settings/environment/DataColorThemeModal.tsx @@ -0,0 +1,104 @@ +import { IconCopy, IconPlus, IconTrash } from '@posthog/icons' +import { LemonButton, LemonInput, LemonLabel, LemonModal, LemonTable } from '@posthog/lemon-ui' +import { useActions, useValues } from 'kea' +import { Form } from 'kea-forms' +import { ColorGlyph } from 'lib/components/SeriesGlyph' +import { LemonField } from 'lib/lemon-ui/LemonField' + +import { dataColorThemesModalLogic } from './dataColorThemeModalLogic' + +export function DataColorThemeModal(): JSX.Element { + const { theme, themeChanged } = useValues(dataColorThemesModalLogic) + const { submitTheme, closeModal, addColor, duplicateColor, removeColor } = useActions(dataColorThemesModalLogic) + + const isNew = theme?.id == null + const isOfficial = theme?.is_global + const title = isOfficial ? 'Official theme' : isNew ? 'Add theme' : 'Edit theme' + + return ( + + Official themes can't be edited. + + Close + +
+ ) : ( + + Save + + ) + } + hasUnsavedInput={themeChanged} + > +
+ + + + Colors + ({ + name: `preset-${index + 1}`, + color, + index, + }))} + columns={[ + { + title: '', + dataIndex: 'color', + key: 'glyph', + render: (_, { color }) => , + width: 24, + }, + { + title: 'Name', + dataIndex: 'name', + key: 'name', + }, + { + title: 'Color', + dataIndex: 'color', + render: (_, { index }) => ( + + + + ), + }, + { + title: '', + key: 'actions', + width: 24, + render: (_, { index }) => + isOfficial ? null : ( +
+ duplicateColor(index)}> + + + removeColor(index)}> + + +
+ ), + }, + ]} + /> + {!isOfficial && ( + } + > + Add color + + )} + + + ) +} diff --git a/frontend/src/scenes/settings/environment/DataColorThemes.tsx b/frontend/src/scenes/settings/environment/DataColorThemes.tsx new file mode 100644 index 0000000000000..d73ed68295f63 --- /dev/null +++ b/frontend/src/scenes/settings/environment/DataColorThemes.tsx @@ -0,0 +1,69 @@ +import { IconBadge } from '@posthog/icons' +import { LemonButton, LemonDialog, LemonLabel, LemonSelect, LemonTable } from '@posthog/lemon-ui' +import { useActions, useValues } from 'kea' +import { LemonTableLink } from 'lib/lemon-ui/LemonTable/LemonTableLink' +import { teamLogic } from 'scenes/teamLogic' + +import { DataColorThemeModal } from './DataColorThemeModal' +import { dataColorThemesLogic } from './dataColorThemesLogic' + +export function DataColorThemes(): JSX.Element { + const { themes: _themes, themesLoading, defaultTheme } = useValues(dataColorThemesLogic) + const { selectTheme } = useActions(dataColorThemesLogic) + + const { currentTeamLoading } = useValues(teamLogic) + const { updateCurrentTeam } = useActions(teamLogic) + + const themes = _themes || [] + + return ( +
+ ( + selectTheme(theme.id)} title={name as string} /> + ), + }, + { + title: 'Official', + dataIndex: 'is_global', + key: 'is_global', + render: (is_global) => (is_global ? : null), + }, + ]} + /> + selectTheme('new')}> + Add theme + + + Default theme + { + const theme = themes.find((theme) => theme.id === value) + LemonDialog.open({ + title: `Change the default data theme to "${theme!.name}"?`, + description: 'This changes the default colors used when visualizing data in insights.', + primaryButton: { + children: 'Change default theme', + onClick: () => updateCurrentTeam({ default_data_theme: value! }), + }, + secondaryButton: { + children: 'Cancel', + }, + }) + }} + loading={themesLoading || currentTeamLoading} + options={themes.map((theme) => ({ value: theme.id, label: theme.name }))} + /> + + +
+ ) +} diff --git a/frontend/src/scenes/settings/environment/dataColorThemeModalLogic.ts b/frontend/src/scenes/settings/environment/dataColorThemeModalLogic.ts new file mode 100644 index 0000000000000..0f46104ec9ea3 --- /dev/null +++ b/frontend/src/scenes/settings/environment/dataColorThemeModalLogic.ts @@ -0,0 +1,81 @@ +import { lemonToast } from '@posthog/lemon-ui' +import { actions, kea, path, reducers } from 'kea' +import { forms } from 'kea-forms' +import api from 'lib/api' + +import { DataColorThemeModel } from '~/types' + +import type { dataColorThemesModalLogicType } from './dataColorThemeModalLogicType' + +export const dataColorThemesModalLogic = kea([ + path(['scenes', 'settings', 'environment', 'dataColorThemesModalLogic']), + actions({ + openModal: (theme) => ({ theme }), + closeModal: true, + addColor: true, + duplicateColor: (index: number) => ({ index }), + removeColor: (index: number) => ({ index }), + }), + reducers({ + theme: { + openModal: (_, { theme }) => theme, + closeModal: () => null, + addColor: (theme) => { + if (theme == null) { + return null + } + return { + ...theme, + colors: [...theme.colors, theme.colors[theme.colors.length - 1] || '#1d4aff'], + } + }, + duplicateColor: (theme, { index }) => { + if (theme == null) { + return null + } + return { + ...theme, + colors: theme.colors.flatMap((color, idx) => (idx === index ? [color, color] : [color])), + } + }, + removeColor: (theme, { index }) => { + if (theme == null) { + return null + } + return { + ...theme, + colors: theme.colors.filter((_, idx) => idx !== index), + } + }, + }, + }), + forms(({ actions }) => ({ + theme: { + defaults: null as DataColorThemeModel | null, + submit: async (formValues, breakpoint) => { + const { id, name, colors } = formValues || {} + const payload: Partial = { + name, + colors, + } + + breakpoint() + + try { + id ? await api.dataColorThemes.update(id, payload) : await api.dataColorThemes.create(payload) + actions.closeModal() + } catch (error: any) { + if (error.data?.attr && error.data?.detail) { + const field = error.data?.attr?.replace(/_/g, ' ') + lemonToast.error(`Error saving data color theme: ${field}: ${error.data.detail}`) + } else { + lemonToast.error(`Error saving data color theme`) + } + } + }, + errors: (theme) => ({ + name: !theme?.name ? 'This field is required' : undefined, + }), + }, + })), +]) diff --git a/frontend/src/scenes/settings/environment/dataColorThemesLogic.ts b/frontend/src/scenes/settings/environment/dataColorThemesLogic.ts new file mode 100644 index 0000000000000..6c35341fd5b74 --- /dev/null +++ b/frontend/src/scenes/settings/environment/dataColorThemesLogic.ts @@ -0,0 +1,33 @@ +import { actions, connect, kea, listeners, path } from 'kea' +import { dataThemeLogic } from 'scenes/dataThemeLogic' + +import { dataColorThemesModalLogic } from './dataColorThemeModalLogic' +import type { dataColorThemesLogicType } from './dataColorThemesLogicType' + +export const dataColorThemesLogic = kea([ + path(['scenes', 'settings', 'environment', 'dataColorThemesLogic']), + connect({ + values: [dataThemeLogic, ['themes', 'themesLoading', 'defaultTheme']], + actions: [dataColorThemesModalLogic, ['openModal']], + }), + actions({ + selectTheme: (id: 'new' | number | null) => ({ id }), + }), + listeners(({ values, actions }) => ({ + selectTheme: ({ id }) => { + // we're not yet initialized + if (values.themes == null || id == null) { + return + } + + if (id === 'new') { + const defaultTheme = values.themes.find((theme) => theme.is_global) + const { id, name, is_global, ...newTheme } = defaultTheme! + actions.openModal(newTheme) + } + + const existingTheme = values.themes.find((theme) => theme.id === id) + actions.openModal(existingTheme) + }, + })), +]) diff --git a/frontend/src/scenes/settings/types.ts b/frontend/src/scenes/settings/types.ts index 3b3f17218c8df..27506de21f70e 100644 --- a/frontend/src/scenes/settings/types.ts +++ b/frontend/src/scenes/settings/types.ts @@ -55,6 +55,7 @@ export type SettingId = | 'autocapture-data-attributes' | 'date-and-time' | 'internal-user-filtering' + | 'data-theme' | 'correlation-analysis' | 'person-display-name' | 'path-cleaning' diff --git a/frontend/src/scenes/trends/trendsDataLogic.ts b/frontend/src/scenes/trends/trendsDataLogic.ts index 3b3a33083adae..043c7313b86f8 100644 --- a/frontend/src/scenes/trends/trendsDataLogic.ts +++ b/frontend/src/scenes/trends/trendsDataLogic.ts @@ -7,6 +7,7 @@ import { BREAKDOWN_NULL_STRING_LABEL, BREAKDOWN_OTHER_NUMERIC_LABEL, BREAKDOWN_OTHER_STRING_LABEL, + getTrendResultCustomizationColorToken, } from 'scenes/insights/utils' import { EventsNode, InsightQueryNode, LifecycleQuery, MathType, TrendsFilter, TrendsQuery } from '~/queries/schema' @@ -69,6 +70,7 @@ export const trendsDataLogic = kea([ 'vizSpecificOptions', 'yAxisScaleType', 'resultCustomizationBy', + 'theme', ], ], actions: [ @@ -248,6 +250,21 @@ export const trendsDataLogic = kea([ }, ], resultCustomizations: [(s) => [s.trendsFilter], (trendsFilter) => trendsFilter?.resultCustomizations], + + getTrendsColor: [ + (s) => [s.resultCustomizationBy, s.resultCustomizations, s.theme], + (resultCustomizationBy, resultCustomizations, theme) => { + return (dataset) => { + const colorToken = getTrendResultCustomizationColorToken( + resultCustomizationBy, + resultCustomizations, + theme, + dataset + ) + return theme?.[colorToken] || null + } + }, + ], })), listeners(({ actions, values }) => ({ diff --git a/frontend/src/scenes/trends/viz/ActionsHorizontalBar.tsx b/frontend/src/scenes/trends/viz/ActionsHorizontalBar.tsx index 0a1bb70663e6f..3da099c8a3e81 100644 --- a/frontend/src/scenes/trends/viz/ActionsHorizontalBar.tsx +++ b/frontend/src/scenes/trends/viz/ActionsHorizontalBar.tsx @@ -1,8 +1,7 @@ import { useValues } from 'kea' import { useEffect, useState } from 'react' -import { dataThemeLogic } from 'scenes/dataThemeLogic' import { insightLogic } from 'scenes/insights/insightLogic' -import { formatBreakdownLabel, getTrendResultCustomizationColorToken } from 'scenes/insights/utils' +import { formatBreakdownLabel } from 'scenes/insights/utils' import { datasetToActorsQuery } from 'scenes/trends/viz/datasetToActorsQuery' import { cohortsModel } from '~/models/cohortsModel' @@ -34,23 +33,13 @@ export function ActionsHorizontalBar({ showPersonsModal = true }: ChartParams): querySource, breakdownFilter, hiddenLegendIndexes, - resultCustomizationBy, - resultCustomizations, + getTrendsColor, + theme, } = useValues(trendsDataLogic(insightProps)) - const { getTheme } = useValues(dataThemeLogic) - const theme = getTheme('posthog') function updateData(): void { const _data = [...indexedResults] - const colorList = indexedResults.map((dataset) => { - const colorToken = getTrendResultCustomizationColorToken( - resultCustomizationBy, - resultCustomizations, - theme, - dataset - ) - return theme[colorToken] - }) + const colorList = indexedResults.map(getTrendsColor) setData([ { @@ -83,7 +72,7 @@ export function ActionsHorizontalBar({ showPersonsModal = true }: ChartParams): if (indexedResults) { updateData() } - }, [indexedResults]) + }, [indexedResults, theme]) return data && total > 0 ? ( 0 ? indexedResults[0].days : [] - const colorList = indexedResults.map((dataset) => { - const colorToken = getTrendResultCustomizationColorToken( - resultCustomizationBy, - resultCustomizations, - theme, - dataset - ) - return theme[colorToken] - }) + const colorList = indexedResults.map(getTrendsColor) as string[] setData([ { @@ -95,7 +83,7 @@ export function ActionsPie({ inSharedMode, showPersonsModal = true, context }: C if (indexedResults) { updateData() } - }, [indexedResults, hiddenLegendIndexes, resultCustomizations, resultCustomizationBy, theme]) + }, [indexedResults, hiddenLegendIndexes]) const onClick = renderingMetadata?.onSegmentClick || diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 86d4b158b0353..26db54d9de1a5 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -551,6 +551,7 @@ export interface TeamType extends TeamBasicType { modifiers?: HogQLQueryModifiers default_modifiers?: HogQLQueryModifiers product_intents?: ProductIntentType[] + default_data_theme?: number } export interface ProductIntentType { @@ -4736,3 +4737,10 @@ export type ReplayTemplateVariableType = { filterGroup?: UniversalFiltersGroupValue noTouch?: boolean } + +export type DataColorThemeModel = { + id: number + name: string + colors: string[] + is_global: boolean +} diff --git a/posthog/admin/__init__.py b/posthog/admin/__init__.py index 1c494050c4836..60d674b532fad 100644 --- a/posthog/admin/__init__.py +++ b/posthog/admin/__init__.py @@ -6,6 +6,7 @@ TeamAdmin, DashboardAdmin, DashboardTemplateAdmin, + DataColorThemeAdmin, InsightAdmin, ExperimentAdmin, FeatureFlagAdmin, @@ -30,6 +31,7 @@ DashboardTemplate, Insight, Experiment, + DataColorTheme, FeatureFlag, AsyncDeletion, InstanceSetting, @@ -53,6 +55,7 @@ admin.site.register(Dashboard, DashboardAdmin) admin.site.register(DashboardTemplate, DashboardTemplateAdmin) admin.site.register(Insight, InsightAdmin) +admin.site.register(DataColorTheme, DataColorThemeAdmin) admin.site.register(Experiment, ExperimentAdmin) admin.site.register(FeatureFlag, FeatureFlagAdmin) diff --git a/posthog/admin/admins/__init__.py b/posthog/admin/admins/__init__.py index a9be1a1068fef..6da88e740f1c8 100644 --- a/posthog/admin/admins/__init__.py +++ b/posthog/admin/admins/__init__.py @@ -2,6 +2,7 @@ from .cohort_admin import CohortAdmin from .dashboard_admin import DashboardAdmin from .dashboard_template_admin import DashboardTemplateAdmin +from .data_color_theme_admin import DataColorThemeAdmin from .data_warehouse_table_admin import DataWarehouseTableAdmin from .experiment_admin import ExperimentAdmin from .feature_flag_admin import FeatureFlagAdmin diff --git a/posthog/admin/admins/data_color_theme_admin.py b/posthog/admin/admins/data_color_theme_admin.py new file mode 100644 index 0000000000000..abe2764dd7e38 --- /dev/null +++ b/posthog/admin/admins/data_color_theme_admin.py @@ -0,0 +1,23 @@ +from django.contrib import admin +from django.utils.html import format_html + +from posthog.models import DataColorTheme + + +class DataColorThemeAdmin(admin.ModelAdmin): + list_display = ( + "id", + "name", + "team_link", + ) + readonly_fields = ("team",) + + @admin.display(description="Team") + def team_link(self, theme: DataColorTheme): + if theme.team is None: + return None + return format_html( + '{}', + theme.team.pk, + theme.team.name, + ) diff --git a/posthog/api/__init__.py b/posthog/api/__init__.py index b488304ab64bb..2e56bf87c0543 100644 --- a/posthog/api/__init__.py +++ b/posthog/api/__init__.py @@ -2,7 +2,7 @@ from rest_framework_extensions.routers import NestedRegistryItem -from posthog.api import project +from posthog.api import data_color_theme, project from posthog.api.routing import DefaultRouterPlusPlus from posthog.batch_exports import http as batch_exports from posthog.settings import EE_AVAILABLE @@ -549,3 +549,7 @@ def register_grandfathered_environment_nested_viewset( ) projects_router.register(r"search", search.SearchViewSet, "project_search", ["project_id"]) + +register_grandfathered_environment_nested_viewset( + r"data_color_themes", data_color_theme.DataColorThemeViewSet, "environment_data_color_themes", ["team_id"] +) diff --git a/posthog/api/data_color_theme.py b/posthog/api/data_color_theme.py new file mode 100644 index 0000000000000..3eb37e0959a69 --- /dev/null +++ b/posthog/api/data_color_theme.py @@ -0,0 +1,75 @@ +from rest_framework import serializers, viewsets +from rest_framework.permissions import SAFE_METHODS, BasePermission +from rest_framework.response import Response +from django.db.models import Q + +from posthog.api.routing import TeamAndOrgViewSetMixin +from posthog.api.shared import UserBasicSerializer +from posthog.auth import SharingAccessTokenAuthentication +from posthog.models import DataColorTheme + + +class GlobalThemePermission(BasePermission): + message = "Only staff users can edit global themes." + + def has_object_permission(self, request, view, obj) -> bool: + if request.method in SAFE_METHODS: + return True + elif view.team == obj.team: + return True + return request.user.is_staff + + +class PublicDataColorThemeSerializer(serializers.ModelSerializer): + class Meta: + model = DataColorTheme + fields = ["id", "name", "colors"] + read_only_fields = ["id", "name", "colors"] + + +class DataColorThemeSerializer(PublicDataColorThemeSerializer): + is_global = serializers.SerializerMethodField() + created_by = UserBasicSerializer(read_only=True) + + class Meta: + model = DataColorTheme + fields = ["id", "name", "colors", "is_global", "created_at", "created_by"] + read_only_fields = [ + "id", + "is_global", + "created_at", + "created_by", + ] + + def create(self, validated_data: dict, *args, **kwargs) -> DataColorTheme: + validated_data["team_id"] = self.context["team_id"] + validated_data["created_by"] = self.context["request"].user + return super().create(validated_data, *args, **kwargs) + + def get_is_global(self, obj): + return obj.team_id is None + + +class DataColorThemeViewSet(TeamAndOrgViewSetMixin, viewsets.ModelViewSet): + scope_object = "INTERNAL" + queryset = DataColorTheme.objects.all().order_by("-created_at") + serializer_class = DataColorThemeSerializer + permission_classes = [GlobalThemePermission] + sharing_enabled_actions = ["retrieve", "list"] + + # override the team scope queryset to also include global themes + def dangerously_get_queryset(self): + query_condition = Q(team_id=self.team_id) | Q(team_id=None) + + return DataColorTheme.objects.filter(query_condition) + + def get_serializer_class(self): + if isinstance(self.request.successful_authenticator, SharingAccessTokenAuthentication): + return PublicDataColorThemeSerializer + return DataColorThemeSerializer + + def list(self, request, *args, **kwargs): + queryset = self.filter_queryset(self.get_queryset()) + + serializer = self.get_serializer(queryset, many=True) + return Response(serializer.data) diff --git a/posthog/api/shared.py b/posthog/api/shared.py index 2515017d00df1..a3419c6dda293 100644 --- a/posthog/api/shared.py +++ b/posthog/api/shared.py @@ -199,13 +199,7 @@ class TeamPublicSerializer(serializers.ModelSerializer): class Meta: model = Team - fields = ( - "id", - "project_id", - "uuid", - "name", - "timezone", - ) + fields = ("id", "project_id", "uuid", "name", "timezone", "default_data_theme") read_only_fields = fields diff --git a/posthog/api/team.py b/posthog/api/team.py index 6a948e002ec09..e4fbb6889fe58 100644 --- a/posthog/api/team.py +++ b/posthog/api/team.py @@ -207,6 +207,7 @@ class Meta: "live_events_token", "product_intents", "capture_dead_clicks", + "default_data_theme", ) read_only_fields = ( "id", diff --git a/posthog/api/test/__snapshots__/test_action.ambr b/posthog/api/test/__snapshots__/test_action.ambr index d92da4aaf17a9..9610bccff447f 100644 --- a/posthog/api/test/__snapshots__/test_action.ambr +++ b/posthog/api/test/__snapshots__/test_action.ambr @@ -83,6 +83,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -178,6 +179,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -505,6 +507,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", diff --git a/posthog/api/test/__snapshots__/test_annotation.ambr b/posthog/api/test/__snapshots__/test_annotation.ambr index d7871ab1851c5..f0abdf6ca4634 100644 --- a/posthog/api/test/__snapshots__/test_annotation.ambr +++ b/posthog/api/test/__snapshots__/test_annotation.ambr @@ -83,6 +83,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -146,6 +147,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -406,6 +408,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", diff --git a/posthog/api/test/__snapshots__/test_decide.ambr b/posthog/api/test/__snapshots__/test_decide.ambr index 39c5cf3557402..09dee17f583f1 100644 --- a/posthog/api/test/__snapshots__/test_decide.ambr +++ b/posthog/api/test/__snapshots__/test_decide.ambr @@ -219,6 +219,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -381,6 +382,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -460,6 +462,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -542,6 +545,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -645,6 +649,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -807,6 +812,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -910,6 +916,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1077,6 +1084,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1198,6 +1206,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", diff --git a/posthog/api/test/__snapshots__/test_early_access_feature.ambr b/posthog/api/test/__snapshots__/test_early_access_feature.ambr index 7e33fb1c9d19e..3714c743f54cd 100644 --- a/posthog/api/test/__snapshots__/test_early_access_feature.ambr +++ b/posthog/api/test/__snapshots__/test_early_access_feature.ambr @@ -51,6 +51,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -199,6 +200,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", diff --git a/posthog/api/test/__snapshots__/test_element.ambr b/posthog/api/test/__snapshots__/test_element.ambr index d6bceb987c788..2a6974ba10e4f 100644 --- a/posthog/api/test/__snapshots__/test_element.ambr +++ b/posthog/api/test/__snapshots__/test_element.ambr @@ -83,6 +83,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", diff --git a/posthog/api/test/__snapshots__/test_feature_flag.ambr b/posthog/api/test/__snapshots__/test_feature_flag.ambr index 177849ed4b92e..0ea32dbfd4212 100644 --- a/posthog/api/test/__snapshots__/test_feature_flag.ambr +++ b/posthog/api/test/__snapshots__/test_feature_flag.ambr @@ -492,6 +492,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -706,6 +707,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1080,6 +1082,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1227,6 +1230,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1533,6 +1537,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1656,6 +1661,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1733,6 +1739,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1803,6 +1810,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", diff --git a/posthog/api/test/__snapshots__/test_insight.ambr b/posthog/api/test/__snapshots__/test_insight.ambr index 0245b9c88b44d..0fb60d4945a19 100644 --- a/posthog/api/test/__snapshots__/test_insight.ambr +++ b/posthog/api/test/__snapshots__/test_insight.ambr @@ -722,6 +722,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -785,6 +786,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -925,6 +927,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1177,6 +1180,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1335,6 +1339,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1480,6 +1485,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1604,6 +1610,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1763,6 +1770,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1861,6 +1869,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1959,6 +1968,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -2029,6 +2039,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", diff --git a/posthog/api/test/__snapshots__/test_organization_feature_flag.ambr b/posthog/api/test/__snapshots__/test_organization_feature_flag.ambr index aa7aba9ddfbb7..3fb3c5fced64a 100644 --- a/posthog/api/test/__snapshots__/test_organization_feature_flag.ambr +++ b/posthog/api/test/__snapshots__/test_organization_feature_flag.ambr @@ -132,6 +132,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -250,6 +251,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -348,6 +350,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -418,6 +421,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -567,6 +571,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -637,6 +642,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -735,6 +741,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -805,6 +812,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -968,6 +976,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1061,6 +1070,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1519,6 +1529,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -2219,6 +2230,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", diff --git a/posthog/api/test/__snapshots__/test_preflight.ambr b/posthog/api/test/__snapshots__/test_preflight.ambr index 79fbf5392e690..87e7d6125b315 100644 --- a/posthog/api/test/__snapshots__/test_preflight.ambr +++ b/posthog/api/test/__snapshots__/test_preflight.ambr @@ -83,6 +83,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", diff --git a/posthog/api/test/__snapshots__/test_survey.ambr b/posthog/api/test/__snapshots__/test_survey.ambr index a0e4260a92f6c..cc252ce76925a 100644 --- a/posthog/api/test/__snapshots__/test_survey.ambr +++ b/posthog/api/test/__snapshots__/test_survey.ambr @@ -198,6 +198,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", diff --git a/posthog/api/test/dashboards/__snapshots__/test_dashboard.ambr b/posthog/api/test/dashboards/__snapshots__/test_dashboard.ambr index 12d1f6f8fff56..4504e0ff3456e 100644 --- a/posthog/api/test/dashboards/__snapshots__/test_dashboard.ambr +++ b/posthog/api/test/dashboards/__snapshots__/test_dashboard.ambr @@ -83,6 +83,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -225,6 +226,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -431,6 +433,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -759,6 +762,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1154,6 +1158,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1224,6 +1229,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1398,6 +1404,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1461,6 +1468,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1601,6 +1609,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1723,6 +1732,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1935,6 +1945,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -2163,6 +2174,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -2261,6 +2273,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -2359,6 +2372,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -2429,6 +2443,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -2531,6 +2546,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -2633,6 +2649,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -2807,6 +2824,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -2927,6 +2945,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -3025,6 +3044,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -3123,6 +3143,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -3193,6 +3214,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -3271,6 +3293,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -3411,6 +3434,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -3533,6 +3557,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -3753,6 +3778,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -3959,6 +3985,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -4127,6 +4154,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -4318,6 +4346,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -4794,6 +4823,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -4917,6 +4947,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -5099,6 +5130,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -5244,6 +5276,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -5353,6 +5386,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -5451,6 +5485,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -5521,6 +5556,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -5591,6 +5627,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -5731,6 +5768,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -5827,6 +5865,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -5897,6 +5936,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -6037,6 +6077,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -6159,6 +6200,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -6359,6 +6401,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -6479,6 +6522,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -6591,6 +6635,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -6689,6 +6734,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -6759,6 +6805,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -6829,6 +6876,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -6969,6 +7017,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -7098,6 +7147,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -7286,6 +7336,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -7381,6 +7432,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -7476,6 +7528,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -7574,6 +7627,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -7672,6 +7726,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -7742,6 +7797,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -7812,6 +7868,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -7966,6 +8023,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -8088,6 +8146,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -8287,6 +8346,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -8493,6 +8553,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -8679,6 +8740,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -8893,6 +8955,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -8995,6 +9058,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -9177,6 +9241,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -9374,6 +9439,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -9497,6 +9563,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -9679,6 +9746,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -9974,6 +10042,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", diff --git a/posthog/api/test/notebooks/__snapshots__/test_notebook.ambr b/posthog/api/test/notebooks/__snapshots__/test_notebook.ambr index 2105778e1cfba..ee129534ab7be 100644 --- a/posthog/api/test/notebooks/__snapshots__/test_notebook.ambr +++ b/posthog/api/test/notebooks/__snapshots__/test_notebook.ambr @@ -83,6 +83,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -178,6 +179,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -457,6 +459,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -564,6 +567,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", diff --git a/posthog/api/test/test_data_color_theme.py b/posthog/api/test/test_data_color_theme.py new file mode 100644 index 0000000000000..94543450f584f --- /dev/null +++ b/posthog/api/test/test_data_color_theme.py @@ -0,0 +1,60 @@ +from rest_framework import status + +from posthog.api.data_color_theme import DataColorTheme +from posthog.models.organization import Organization +from posthog.models.team.team import Team +from posthog.test.base import APIBaseTest + + +class TestDataColorTheme(APIBaseTest): + def test_can_fetch_public_themes(self) -> None: + response = self.client.get(f"/api/environments/{self.team.pk}/data_color_themes") + + assert response.status_code == status.HTTP_200_OK + assert response.data[0]["is_global"] + + def test_can_fetch_own_themes(self) -> None: + other_org = Organization.objects.create(name="other org") + other_team = Team.objects.create(organization=other_org, name="other project") + DataColorTheme.objects.create(name="Custom theme 1", colors=[], team=self.team) + DataColorTheme.objects.create(name="Custom theme 2", colors=[], team=other_team) + + response = self.client.get(f"/api/environments/{self.team.pk}/data_color_themes") + + assert response.status_code == status.HTTP_200_OK + assert len(response.data) == 2 + assert response.data[1]["name"] == "Custom theme 1" + + def test_can_edit_own_themes(self) -> None: + theme = DataColorTheme.objects.create(name="Original name", colors=[], team=self.team) + + response = self.client.patch( + f"/api/environments/{self.team.pk}/data_color_themes/{theme.pk}", {"name": "New name"} + ) + + assert response.status_code == status.HTTP_200_OK + assert DataColorTheme.objects.get(pk=theme.pk).name == "New name" + + def test_can_not_edit_public_themes(self) -> None: + theme = DataColorTheme.objects.first() + assert theme + + response = self.client.patch( + f"/api/environments/{self.team.pk}/data_color_themes/{theme.pk}", {"name": "New name"} + ) + + assert response.status_code == status.HTTP_403_FORBIDDEN + assert DataColorTheme.objects.get(pk=theme.pk).name == "Default Theme" + + def test_can_edit_public_themes_as_staff(self) -> None: + self.user.is_staff = True + self.user.save() + theme = DataColorTheme.objects.first() + assert theme + + response = self.client.patch( + f"/api/environments/{self.team.pk}/data_color_themes/{theme.pk}", {"name": "New name"} + ) + + assert response.status_code == status.HTTP_200_OK + assert DataColorTheme.objects.get(pk=theme.pk).name == "New name" diff --git a/posthog/migrations/0521_datacolortheme_datacolortheme_unique_name_per_team.py b/posthog/migrations/0521_datacolortheme_datacolortheme_unique_name_per_team.py new file mode 100644 index 0000000000000..f8827edb4d413 --- /dev/null +++ b/posthog/migrations/0521_datacolortheme_datacolortheme_unique_name_per_team.py @@ -0,0 +1,67 @@ +# Generated by Django 4.2.15 on 2024-11-20 18:59 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +def add_default_themes(apps, schema_editor): + DataColorTheme = apps.get_model("posthog", "DataColorTheme") + + DataColorTheme.objects.create( + name="Default Theme", + colors=[ + "#1d4aff", + "#621da6", + "#42827e", + "#ce0e74", + "#f14f58", + "#7c440e", + "#529a0a", + "#0476fb", + "#fe729e", + "#35416b", + "#41cbc4", + "#b64b02", + "#e4a604", + "#a56eff", + "#30d5c8", + ], + ) + + +def remove_default_themes(apps, schema_editor): + # no-op, as table will be dropped on rollback anyway + pass + + +class Migration(migrations.Migration): + dependencies = [ + ("posthog", "0520_experiment_metrics_secondary"), + ] + + operations = [ + migrations.CreateModel( + name="DataColorTheme", + fields=[ + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("name", models.CharField(max_length=100)), + ("colors", models.JSONField(default=list)), + ( + "team", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="posthog.team", null=True, blank=True + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True, null=True)), + ( + "created_by", + models.ForeignKey( + blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL + ), + ), + ("deleted", models.BooleanField(blank=True, null=True)), + ], + ), + migrations.RunPython(add_default_themes, remove_default_themes), + ] diff --git a/posthog/migrations/0522_team_default_data_theme.py b/posthog/migrations/0522_team_default_data_theme.py new file mode 100644 index 0000000000000..0ebfd1b13ba2e --- /dev/null +++ b/posthog/migrations/0522_team_default_data_theme.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.15 on 2024-11-22 10:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("posthog", "0521_datacolortheme_datacolortheme_unique_name_per_team"), + ] + + operations = [ + migrations.AddField( + model_name="team", + name="default_data_theme", + field=models.IntegerField(blank=True, null=True), + ), + ] diff --git a/posthog/migrations/max_migration.txt b/posthog/migrations/max_migration.txt index 31b8c212f629f..65e1d23aa080a 100644 --- a/posthog/migrations/max_migration.txt +++ b/posthog/migrations/max_migration.txt @@ -1 +1 @@ -0520_experiment_metrics_secondary +0522_team_default_data_theme diff --git a/posthog/models/__init__.py b/posthog/models/__init__.py index 90579100c6c10..49fe688b18818 100644 --- a/posthog/models/__init__.py +++ b/posthog/models/__init__.py @@ -25,6 +25,7 @@ from .comment import Comment from .dashboard import Dashboard from .dashboard_templates import DashboardTemplate +from .data_color_theme import DataColorTheme from .dashboard_tile import DashboardTile, Text from .early_access_feature import EarlyAccessFeature from .element import Element @@ -96,6 +97,7 @@ "Dashboard", "DashboardTile", "DashboardTemplate", + "DataColorTheme", "DeletionType", "EarlyAccessFeature", "Element", diff --git a/posthog/models/data_color_theme.py b/posthog/models/data_color_theme.py new file mode 100644 index 0000000000000..c13b6fb79dec4 --- /dev/null +++ b/posthog/models/data_color_theme.py @@ -0,0 +1,16 @@ +from django.db import models + +from posthog.models.team import Team + + +class DataColorTheme(models.Model): + team = models.ForeignKey(Team, on_delete=models.CASCADE, null=True, blank=True) + + name = models.CharField(max_length=100) + colors = models.JSONField(default=list) + created_at = models.DateTimeField(auto_now_add=True, blank=True, null=True) + created_by = models.ForeignKey("User", on_delete=models.SET_NULL, null=True, blank=True) + deleted = models.BooleanField(blank=True, null=True) + + def __str__(self): + return self.name diff --git a/posthog/models/filters/test/__snapshots__/test_filter.ambr b/posthog/models/filters/test/__snapshots__/test_filter.ambr index 38d231d6b6360..4ee5a4ea06f86 100644 --- a/posthog/models/filters/test/__snapshots__/test_filter.ambr +++ b/posthog/models/filters/test/__snapshots__/test_filter.ambr @@ -51,6 +51,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -121,6 +122,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -191,6 +193,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -261,6 +264,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -331,6 +335,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", diff --git a/posthog/models/team/team.py b/posthog/models/team/team.py index b87ed07c84ee0..5437be43231af 100644 --- a/posthog/models/team/team.py +++ b/posthog/models/team/team.py @@ -285,6 +285,8 @@ class Meta: blank=True, ) # Dashboard shown on project homepage + default_data_theme = models.IntegerField(null=True, blank=True) + # Generic field for storing any team-specific context that is more temporary in nature and thus # likely doesn't deserve a dedicated column. Can be used for things like settings and overrides # during feature releases. diff --git a/posthog/schema.py b/posthog/schema.py index 8707ed526342e..6131cfd745868 100644 --- a/posthog/schema.py +++ b/posthog/schema.py @@ -5964,6 +5964,7 @@ class RetentionQuery(BaseModel): extra="forbid", ) aggregation_group_type_index: Optional[int] = Field(default=None, description="Groups aggregation") + dataColorTheme: Optional[float] = Field(default=None, description="Colors used in the insight's visualization") dateRange: Optional[InsightDateRange] = Field(default=None, description="Date range for the query") filterTestAccounts: Optional[bool] = Field( default=False, description="Exclude internal and test users by applying the respective filters" @@ -6010,6 +6011,7 @@ class StickinessQuery(BaseModel): extra="forbid", ) compareFilter: Optional[CompareFilter] = Field(default=None, description="Compare to date range") + dataColorTheme: Optional[float] = Field(default=None, description="Colors used in the insight's visualization") dateRange: Optional[InsightDateRange] = Field(default=None, description="Date range for the query") filterTestAccounts: Optional[bool] = Field( default=False, description="Exclude internal and test users by applying the respective filters" @@ -6061,6 +6063,7 @@ class TrendsQuery(BaseModel): aggregation_group_type_index: Optional[int] = Field(default=None, description="Groups aggregation") breakdownFilter: Optional[BreakdownFilter] = Field(default=None, description="Breakdown of the events and actions") compareFilter: Optional[CompareFilter] = Field(default=None, description="Compare to date range") + dataColorTheme: Optional[float] = Field(default=None, description="Colors used in the insight's visualization") dateRange: Optional[InsightDateRange] = Field(default=None, description="Date range for the query") filterTestAccounts: Optional[bool] = Field( default=False, description="Exclude internal and test users by applying the respective filters" @@ -6256,6 +6259,7 @@ class FunnelsQuery(BaseModel): ) aggregation_group_type_index: Optional[int] = Field(default=None, description="Groups aggregation") breakdownFilter: Optional[BreakdownFilter] = Field(default=None, description="Breakdown of the events and actions") + dataColorTheme: Optional[float] = Field(default=None, description="Colors used in the insight's visualization") dateRange: Optional[InsightDateRange] = Field(default=None, description="Date range for the query") filterTestAccounts: Optional[bool] = Field( default=False, description="Exclude internal and test users by applying the respective filters" @@ -6308,6 +6312,7 @@ class InsightsQueryBaseFunnelsQueryResponse(BaseModel): extra="forbid", ) aggregation_group_type_index: Optional[int] = Field(default=None, description="Groups aggregation") + dataColorTheme: Optional[float] = Field(default=None, description="Colors used in the insight's visualization") dateRange: Optional[InsightDateRange] = Field(default=None, description="Date range for the query") filterTestAccounts: Optional[bool] = Field( default=False, description="Exclude internal and test users by applying the respective filters" @@ -6347,6 +6352,7 @@ class InsightsQueryBaseLifecycleQueryResponse(BaseModel): extra="forbid", ) aggregation_group_type_index: Optional[int] = Field(default=None, description="Groups aggregation") + dataColorTheme: Optional[float] = Field(default=None, description="Colors used in the insight's visualization") dateRange: Optional[InsightDateRange] = Field(default=None, description="Date range for the query") filterTestAccounts: Optional[bool] = Field( default=False, description="Exclude internal and test users by applying the respective filters" @@ -6386,6 +6392,7 @@ class InsightsQueryBasePathsQueryResponse(BaseModel): extra="forbid", ) aggregation_group_type_index: Optional[int] = Field(default=None, description="Groups aggregation") + dataColorTheme: Optional[float] = Field(default=None, description="Colors used in the insight's visualization") dateRange: Optional[InsightDateRange] = Field(default=None, description="Date range for the query") filterTestAccounts: Optional[bool] = Field( default=False, description="Exclude internal and test users by applying the respective filters" @@ -6425,6 +6432,7 @@ class InsightsQueryBaseRetentionQueryResponse(BaseModel): extra="forbid", ) aggregation_group_type_index: Optional[int] = Field(default=None, description="Groups aggregation") + dataColorTheme: Optional[float] = Field(default=None, description="Colors used in the insight's visualization") dateRange: Optional[InsightDateRange] = Field(default=None, description="Date range for the query") filterTestAccounts: Optional[bool] = Field( default=False, description="Exclude internal and test users by applying the respective filters" @@ -6464,6 +6472,7 @@ class InsightsQueryBaseTrendsQueryResponse(BaseModel): extra="forbid", ) aggregation_group_type_index: Optional[int] = Field(default=None, description="Groups aggregation") + dataColorTheme: Optional[float] = Field(default=None, description="Colors used in the insight's visualization") dateRange: Optional[InsightDateRange] = Field(default=None, description="Date range for the query") filterTestAccounts: Optional[bool] = Field( default=False, description="Exclude internal and test users by applying the respective filters" @@ -6503,6 +6512,7 @@ class LifecycleQuery(BaseModel): extra="forbid", ) aggregation_group_type_index: Optional[int] = Field(default=None, description="Groups aggregation") + dataColorTheme: Optional[float] = Field(default=None, description="Colors used in the insight's visualization") dateRange: Optional[InsightDateRange] = Field(default=None, description="Date range for the query") filterTestAccounts: Optional[bool] = Field( default=False, description="Exclude internal and test users by applying the respective filters" @@ -6851,6 +6861,7 @@ class PathsQuery(BaseModel): extra="forbid", ) aggregation_group_type_index: Optional[int] = Field(default=None, description="Groups aggregation") + dataColorTheme: Optional[float] = Field(default=None, description="Colors used in the insight's visualization") dateRange: Optional[InsightDateRange] = Field(default=None, description="Date range for the query") filterTestAccounts: Optional[bool] = Field( default=False, description="Exclude internal and test users by applying the respective filters" diff --git a/posthog/session_recordings/test/__snapshots__/test_session_recordings.ambr b/posthog/session_recordings/test/__snapshots__/test_session_recordings.ambr index 220b74452fa05..f0b9edd3cfabb 100644 --- a/posthog/session_recordings/test/__snapshots__/test_session_recordings.ambr +++ b/posthog/session_recordings/test/__snapshots__/test_session_recordings.ambr @@ -51,6 +51,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -121,6 +122,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -191,6 +193,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -261,6 +264,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -331,6 +335,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -509,6 +514,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -604,6 +610,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -980,6 +987,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1082,6 +1090,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1152,6 +1161,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1222,6 +1232,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1292,6 +1303,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1362,6 +1374,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1432,6 +1445,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1534,6 +1548,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1736,6 +1751,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -1876,6 +1892,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -2344,6 +2361,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -2484,6 +2502,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -2639,6 +2658,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -2997,6 +3017,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -3156,6 +3177,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -3620,6 +3642,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -3792,6 +3815,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -4073,6 +4097,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -4302,6 +4327,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -4442,6 +4468,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -5317,6 +5344,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -5457,6 +5485,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -5903,6 +5932,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -6064,6 +6094,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -6572,6 +6603,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -6731,6 +6763,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -7181,6 +7214,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -7321,6 +7355,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", diff --git a/posthog/tasks/test/__snapshots__/test_process_scheduled_changes.ambr b/posthog/tasks/test/__snapshots__/test_process_scheduled_changes.ambr index 64eda354dd032..03a08d8b9a254 100644 --- a/posthog/tasks/test/__snapshots__/test_process_scheduled_changes.ambr +++ b/posthog/tasks/test/__snapshots__/test_process_scheduled_changes.ambr @@ -122,6 +122,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -390,6 +391,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", diff --git a/posthog/test/__snapshots__/test_feature_flag.ambr b/posthog/test/__snapshots__/test_feature_flag.ambr index fefcbbffd5d46..2f71e8742693d 100644 --- a/posthog/test/__snapshots__/test_feature_flag.ambr +++ b/posthog/test/__snapshots__/test_feature_flag.ambr @@ -175,6 +175,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -351,6 +352,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config", @@ -695,6 +697,7 @@ "posthog_team"."live_events_columns", "posthog_team"."recording_domains", "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", "posthog_team"."extra_settings", "posthog_team"."modifiers", "posthog_team"."correlation_config",