diff --git a/frontend/src/lib/constants.tsx b/frontend/src/lib/constants.tsx index ce310a14fbe9c..6cc9219c16e05 100644 --- a/frontend/src/lib/constants.tsx +++ b/frontend/src/lib/constants.tsx @@ -164,7 +164,8 @@ export const FEATURE_FLAGS = { PRODUCT_SPECIFIC_ONBOARDING: 'product-specific-onboarding', // owner: @raquelmsmith REDIRECT_SIGNUPS_TO_INSTANCE: 'redirect-signups-to-instance', // owner: @raquelmsmith APPS_AND_EXPORTS_UI: 'apps-and-exports-ui', // owner: @benjackwhite - HOGQL_DASHBOARD_CARDS: 'hogql-dashboard-cards', // owner: @thmsobrmlr + QUERY_BASED_DASHBOARD_CARDS: 'query-based-dashboard-cards', // owner: @thmsobrmlr + QUERY_BASED_INSIGHTS_SAVING: 'query-based-insights-saving', // owner: @thmsobrmlr HOGQL_DASHBOARD_ASYNC: 'hogql-dashboard-async', // owner: @webjunkie WEBHOOKS_DENYLIST: 'webhooks-denylist', // owner: #team-pipeline PERSONS_HOGQL_QUERY: 'persons-hogql-query', // owner: @mariusandra diff --git a/frontend/src/scenes/insights/insightDataLogic.tsx b/frontend/src/scenes/insights/insightDataLogic.tsx index 9088640e828ab..1e8eaf4153423 100644 --- a/frontend/src/scenes/insights/insightDataLogic.tsx +++ b/frontend/src/scenes/insights/insightDataLogic.tsx @@ -105,7 +105,7 @@ export const insightDataLogic = kea([ selectors({ useQueryDashboardCards: [ (s) => [s.featureFlags], - (featureFlags) => !!featureFlags[FEATURE_FLAGS.HOGQL_DASHBOARD_CARDS], + (featureFlags) => !!featureFlags[FEATURE_FLAGS.QUERY_BASED_DASHBOARD_CARDS], ], query: [ diff --git a/frontend/src/scenes/insights/insightLogic.test.ts b/frontend/src/scenes/insights/insightLogic.test.ts index 0cfb2792ae8a6..2b543c1a926e8 100644 --- a/frontend/src/scenes/insights/insightLogic.test.ts +++ b/frontend/src/scenes/insights/insightLogic.test.ts @@ -310,7 +310,6 @@ describe('insightLogic', () => { short_id: Insight42, query: { kind: NodeKind.TimeToSeeDataSessionsQuery }, }), - legacyFilters: {}, }) .delay(1) // do not override the insight if querying with different filters @@ -340,10 +339,25 @@ describe('insightLogic', () => { await expectLogic(logic) .toMatchValues({ legacyInsight: insight, - legacyFilters: partial({ - events: [partial({ id: 3 })], - properties: [partial({ value: 'a' })], - }), + queryBasedInsight: { + short_id: Insight42, + query: { + kind: 'InsightVizNode', + source: { + kind: 'TrendsQuery', + properties: { + type: 'AND', + values: [ + { + type: 'AND', + values: [partial({ value: 'a' })], + }, + ], + }, + series: [partial({ event: 3 })], + }, + }, + }, }) .delay(1) .toNotHaveDispatchedActions(['setFilters', 'updateInsight']) @@ -540,7 +554,6 @@ describe('insightLogic', () => { .toDispatchActions(savedInsightsLogic, ['loadInsights']) .toMatchValues({ savedInsight: partial({ filters: partial({ insight: InsightType.FUNNELS }) }), - legacyFilters: partial({ insight: InsightType.FUNNELS }), legacyInsight: partial({ id: 12, short_id: Insight12, name: 'New Insight (copy)' }), queryBasedInsight: partial({ id: 12, short_id: Insight12, name: 'New Insight (copy)' }), insightChanged: false, @@ -742,7 +755,6 @@ describe('insightLogic', () => { `api/projects/${MOCK_TEAM_ID}/insights/`, { derived_name: 'DataTableNode query', - filters: {}, query: { kind: 'DataTableNode', }, diff --git a/frontend/src/scenes/insights/insightLogic.ts b/frontend/src/scenes/insights/insightLogic.ts index 402a3557d09e8..366e9e7edb8ef 100644 --- a/frontend/src/scenes/insights/insightLogic.ts +++ b/frontend/src/scenes/insights/insightLogic.ts @@ -3,8 +3,9 @@ import { actions, connect, events, kea, key, listeners, path, props, reducers, s import { loaders } from 'kea-loaders' import { router } from 'kea-router' import api from 'lib/api' -import { DashboardPrivilegeLevel } from 'lib/constants' +import { DashboardPrivilegeLevel, FEATURE_FLAGS } from 'lib/constants' import { lemonToast } from 'lib/lemon-ui/LemonToast/LemonToast' +import { featureFlagLogic } from 'lib/logic/featureFlagLogic' import { objectsEqual } from 'lib/utils' import { eventUsageLogic, InsightEventSource } from 'lib/utils/eventUsageLogic' import { insightSceneLogic } from 'scenes/insights/insightSceneLogic' @@ -21,8 +22,10 @@ import { dashboardsModel } from '~/models/dashboardsModel' import { groupsModel } from '~/models/groupsModel' import { insightsModel } from '~/models/insightsModel' import { tagsModel } from '~/models/tagsModel' +import { queryNodeToFilter } from '~/queries/nodes/InsightQuery/utils/queryNodeToFilter' import { getQueryBasedInsightModel } from '~/queries/nodes/InsightViz/utils' import { InsightVizNode } from '~/queries/schema' +import { isInsightVizNode } from '~/queries/utils' import { FilterType, InsightLogicProps, InsightModel, InsightShortId, ItemMode, SetInsightOptions } from '~/types' import { teamLogic } from '../teamLogic' @@ -66,6 +69,8 @@ export const insightLogic = kea([ ['mathDefinitions'], userLogic, ['user'], + featureFlagLogic, + ['featureFlags'], ], actions: [tagsModel, ['loadTags']], logic: [eventUsageLogic, dashboardsModel], @@ -271,17 +276,6 @@ export const insightLogic = kea([ return { ...state, dashboards: state.dashboards?.filter((d) => d !== id) } }, }, - /* filters contains the in-flight filters, might not (yet?) be the same as insight.filters */ - legacyFilters: [ - () => props.cachedInsight?.filters || ({} as Partial), - { - setFilters: (_, { filters }) => cleanFilters(filters), - setInsight: (state, { insight: { filters }, options: { overrideFilter } }) => - overrideFilter ? cleanFilters(filters || {}) : state, - loadInsightSuccess: (state, { legacyInsight }) => - Object.keys(state).length === 0 && legacyInsight.filters ? legacyInsight.filters : state, - }, - ], /** The insight's state as it is in the database. */ savedInsight: [ () => props.cachedInsight || ({} as InsightModel), @@ -316,6 +310,10 @@ export const insightLogic = kea([ ], })), selectors({ + queryBasedInsightSaving: [ + (s) => [s.featureFlags], + (featureFlags) => !!featureFlags[FEATURE_FLAGS.QUERY_BASED_INSIGHTS_SAVING], + ], queryBasedInsight: [(s) => [s.legacyInsight], (legacyInsight) => getQueryBasedInsightModel(legacyInsight)], insightProps: [() => [(_, props) => props], (props): InsightLogicProps => props], isInDashboardContext: [() => [(_, props) => props], ({ dashboardId }) => !!dashboardId], @@ -364,10 +362,19 @@ export const insightLogic = kea([ listeners(({ actions, values }) => ({ saveInsight: async ({ redirectToViewMode }) => { const insightNumericId = - values.legacyInsight.id || - (values.legacyInsight.short_id ? await getInsightId(values.legacyInsight.short_id) : undefined) - const { name, description, favorited, filters, query, deleted, dashboards, tags } = values.legacyInsight + values.queryBasedInsight.id || + (values.queryBasedInsight.short_id ? await getInsightId(values.queryBasedInsight.short_id) : undefined) + const { name, description, favorited, deleted, dashboards, tags } = values.legacyInsight + let savedInsight: InsightModel + let filters + let query + + if (!values.queryBasedInsightSaving && isInsightVizNode(values.queryBasedInsight.query)) { + filters = queryNodeToFilter(values.queryBasedInsight.query.source) + } else { + query = values.queryBasedInsight.query + } try { // We don't want to send ALL the insight properties back to the API, so only grabbing fields that might have changed @@ -377,7 +384,7 @@ export const insightLogic = kea([ description, favorited, filters, - query: query ? query : null, + query, deleted, saved: true, dashboards, @@ -428,15 +435,23 @@ export const insightLogic = kea([ } }, saveAsNamingSuccess: async ({ name }) => { + let filters + let query + if (!values.queryBasedInsightSaving && isInsightVizNode(values.queryBasedInsight.query)) { + filters = queryNodeToFilter(values.queryBasedInsight.query.source) + } else { + query = values.queryBasedInsight.query + } + const insight: InsightModel = await api.create(`api/projects/${teamLogic.values.currentTeamId}/insights/`, { name, - filters: values.legacyFilters, - query: values.legacyInsight.query, + filters, + query, saved: true, }) lemonToast.info( `You're now working on a copy of ${ - values.queryBasedInsight.name ?? values.queryBasedInsight.derived_name + values.queryBasedInsight.name || values.queryBasedInsight.derived_name }` ) actions.setInsight(insight, { fromPersistentApi: true, overrideFilter: true })