diff --git a/frontend/__snapshots__/scenes-app-features--new-feature-flag--dark.png b/frontend/__snapshots__/scenes-app-features--new-feature-flag--dark.png index aef2d5474fdd7..a89ab7c32e206 100644 Binary files a/frontend/__snapshots__/scenes-app-features--new-feature-flag--dark.png and b/frontend/__snapshots__/scenes-app-features--new-feature-flag--dark.png differ diff --git a/frontend/__snapshots__/scenes-app-features--new-feature-flag--light.png b/frontend/__snapshots__/scenes-app-features--new-feature-flag--light.png index fdc2a2e6ab914..b9ca780a2319c 100644 Binary files a/frontend/__snapshots__/scenes-app-features--new-feature-flag--light.png and b/frontend/__snapshots__/scenes-app-features--new-feature-flag--light.png differ diff --git a/frontend/__snapshots__/scenes-app-saved-insights--list-view--light.png b/frontend/__snapshots__/scenes-app-saved-insights--list-view--light.png index 36c970c9976ad..cf3891a6bbec1 100644 Binary files a/frontend/__snapshots__/scenes-app-saved-insights--list-view--light.png and b/frontend/__snapshots__/scenes-app-saved-insights--list-view--light.png differ diff --git a/frontend/src/scenes/early-access-features/EarlyAccessFeature.tsx b/frontend/src/scenes/early-access-features/EarlyAccessFeature.tsx index 8578f06d5acbb..34f0fc91ff606 100644 --- a/frontend/src/scenes/early-access-features/EarlyAccessFeature.tsx +++ b/frontend/src/scenes/early-access-features/EarlyAccessFeature.tsx @@ -1,24 +1,21 @@ import { IconFlag, IconQuestion, IconX } from '@posthog/icons' import { LemonButton, LemonDivider, LemonInput, LemonSkeleton, LemonTag, LemonTextArea, Link } from '@posthog/lemon-ui' import clsx from 'clsx' -import { BindLogic, useActions, useValues } from 'kea' +import { useActions, useValues } from 'kea' import { Form } from 'kea-forms' import { router } from 'kea-router' import { FlagSelector } from 'lib/components/FlagSelector' import { NotFound } from 'lib/components/NotFound' import { PageHeader } from 'lib/components/PageHeader' -import { PropertyFilters } from 'lib/components/PropertyFilters/PropertyFilters' -import { TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types' import { LemonDialog } from 'lib/lemon-ui/LemonDialog' import { LemonField } from 'lib/lemon-ui/LemonField' import { LemonTabs } from 'lib/lemon-ui/LemonTabs' -import { featureFlagLogic } from 'scenes/feature-flags/featureFlagLogic' -import { personsLogic, PersonsLogicProps } from 'scenes/persons/personsLogic' -import { PersonsSearch } from 'scenes/persons/PersonsSearch' -import { PersonsTable } from 'scenes/persons/PersonsTable' +import { useState } from 'react' import { SceneExport } from 'scenes/sceneTypes' import { urls } from 'scenes/urls' +import { Query } from '~/queries/Query/Query' +import { Node, NodeKind, QuerySchema } from '~/queries/schema' import { EarlyAccessFeatureStage, EarlyAccessFeatureTabs, @@ -48,6 +45,7 @@ export function EarlyAccessFeature({ id }: { id?: string } = {}): JSX.Element { isEarlyAccessFeatureSubmitting, isEditingFeature, earlyAccessFeatureMissing, + implementOptInInstructionsModal, } = useValues(earlyAccessFeatureLogic) const { submitEarlyAccessFeatureRequest, @@ -55,6 +53,7 @@ export function EarlyAccessFeature({ id }: { id?: string } = {}): JSX.Element { editFeature, updateStage, deleteEarlyAccessFeature, + toggleImplementOptInInstructionsModal, } = useActions(earlyAccessFeatureLogic) const isNewEarlyAccessFeature = id === 'new' || id === undefined @@ -179,118 +178,163 @@ export function EarlyAccessFeature({ id }: { id?: string } = {}): JSX.Element { } delimited /> -
+
{isNewEarlyAccessFeature && ( )} - {'feature_flag' in earlyAccessFeature ? ( - -
- - earlyAccessFeature.feature_flag && - router.actions.push(urls.featureFlag(earlyAccessFeature.feature_flag.id)) - } - icon={} - > - {earlyAccessFeature.feature_flag.key} - -
-
- ) : ( - A feature flag will be generated from feature name if not provided} - > - {({ value, onChange }) => ( -
- - {value && ( + +
+
+ {'feature_flag' in earlyAccessFeature ? ( + +
} - size="small" - onClick={() => onChange(undefined)} - aria-label="close" - /> + type="secondary" + onClick={() => + earlyAccessFeature.feature_flag && + router.actions.push( + urls.featureFlag(earlyAccessFeature.feature_flag.id) + ) + } + icon={} + > + {earlyAccessFeature.feature_flag.key} + +
+
+ ) : ( + A feature flag will be generated from feature name if not provided} + > + {({ value, onChange }) => ( +
+ + {value && ( + } + size="small" + onClick={() => onChange(undefined)} + aria-label="close" + /> + )} +
)} -
+ )} - - )} - {isEditingFeature || isNewEarlyAccessFeature ? ( - <> - ) : ( -
- Stage -
- - {earlyAccessFeature.stage} - -
- )} - {isEditingFeature || isNewEarlyAccessFeature ? ( - - - - ) : ( -
- Description -
- {earlyAccessFeature.description ? ( - earlyAccessFeature.description - ) : ( - No description - )} + {isEditingFeature || isNewEarlyAccessFeature ? ( + <> + ) : ( +
+ Stage +
+ + {earlyAccessFeature.stage} + +
+ )} +
+
+
+ {isEditingFeature || isNewEarlyAccessFeature ? ( + + + + ) : ( +
+ Description +
+ {earlyAccessFeature.description ? ( + earlyAccessFeature.description + ) : ( + No description + )} +
+
+ )}
- )} - {isEditingFeature || isNewEarlyAccessFeature ? ( - - - - ) : ( -
- Documentation URL -
- {earlyAccessFeature.documentation_url ? ( - earlyAccessFeature.documentation_url - ) : ( - No documentation URL - )} -
+
+ {isEditingFeature || isNewEarlyAccessFeature ? ( + + + + ) : ( +
+ Documentation URL +
+ {earlyAccessFeature.documentation_url ? ( + + {earlyAccessFeature.documentation_url} + + ) : ( + No documentation URL + )} +
+
+ )}
- )} +
{!isEditingFeature && !isNewEarlyAccessFeature && 'id' in earlyAccessFeature && ( -
+ <> + +
+
+

Users

+

+ When a user opts in or out of the feature they will be listed here. You can choose + to{' '} + + implement your own opt-in interface or use our provided app. + +

+
+ } + type="secondary" + > + Implement public opt-in + +
-
+ )}
+ + {'id' in earlyAccessFeature ? ( + + ) : null} ) } @@ -326,10 +370,8 @@ function featureFlagEnrolmentFilter(earlyAccessFeature: EarlyAccessFeatureType, } export function PersonList({ earlyAccessFeature }: PersonListProps): JSX.Element { - const { implementOptInInstructionsModal, activeTab } = useValues(earlyAccessFeatureLogic) - const { toggleImplementOptInInstructionsModal, setActiveTab } = useActions(earlyAccessFeatureLogic) - - const { featureFlag } = useValues(featureFlagLogic({ id: earlyAccessFeature.feature_flag.id || 'link' })) + const { activeTab } = useValues(earlyAccessFeatureLogic) + const { setActiveTab } = useActions(earlyAccessFeatureLogic) const key = '$feature_enrollment/' + earlyAccessFeature.feature_flag.key @@ -354,14 +396,6 @@ export function PersonList({ earlyAccessFeature }: PersonListProps): JSX.Element value: ['true'], }, ]} - emptyState={ -
- No manual opt-ins. Manually opted-in people will appear here. Start by{' '} - - implementing public opt-in - -
- } /> ), @@ -380,113 +414,44 @@ export function PersonList({ earlyAccessFeature }: PersonListProps): JSX.Element value: ['false'], }, ]} - emptyState={ -
- No manual opt-outs. Manually opted-out people will appear here. Start by{' '} - - implementing public opt-out - -
- } /> ), }, ]} /> - - ) } interface PersonsTableByFilterProps { properties: PersonPropertyFilter[] - emptyState?: JSX.Element recordingsFilters: Partial } -export function PersonsTableByFilter(props: PersonsTableByFilterProps): JSX.Element { - const personsLogicProps: PersonsLogicProps = { - cohort: undefined, - syncWithUrl: false, - fixedProperties: props.properties, - } +function PersonsTableByFilter({ recordingsFilters, properties }: PersonsTableByFilterProps): JSX.Element { + const [query, setQuery] = useState({ + kind: NodeKind.DataTableNode, + source: { + kind: NodeKind.PersonsNode, + fixedProperties: properties, + }, + full: true, + propertiesViaUrl: false, + }) return ( - - - - ) -} - -interface PersonsTableByFilterComponentProps { - emptyState?: JSX.Element - recordingsFilters: Partial -} - -function PersonsTableByFilterComponent({ - emptyState, - recordingsFilters, -}: PersonsTableByFilterComponentProps): JSX.Element { - const { toggleImplementOptInInstructionsModal } = useActions(earlyAccessFeatureLogic) - - const { persons, personsLoading, listFilters } = useValues(personsLogic) - const { loadPersons, setListFilters } = useActions(personsLogic) - - return ( -
-
- +
+ {/* NOTE: This is a bit of a placement hack - ideally we would be able to add it to the Query */} +
+ + View recordings +
-
- { - setListFilters({ properties }) - loadPersons() - }} - endpoint="person" - taxonomicGroupTypes={[TaxonomicFilterGroupType.PersonProperties]} - showConditionBadge - /> -
- } - > - Implement public opt-in - - { - router.actions.push(urls.replay(ReplayTabs.Recent, recordingsFilters)) - }} - type="secondary" - disabledReason={ - personsLoading ? 'Loading…' : persons.results.length === 0 ? 'No users to view' : undefined - } - > - View recordings - -
-
- loadPersons(persons.previous)} - loadNext={() => loadPersons(persons.next)} - compact={true} - extraColumns={[]} - emptyState={emptyState} - /> +
) } diff --git a/frontend/src/scenes/early-access-features/InstructionsModal.tsx b/frontend/src/scenes/early-access-features/InstructionsModal.tsx index c0b05f009d1f7..61c87c650a8c0 100644 --- a/frontend/src/scenes/early-access-features/InstructionsModal.tsx +++ b/frontend/src/scenes/early-access-features/InstructionsModal.tsx @@ -7,12 +7,12 @@ import { preflightLogic } from 'scenes/PreflightCheck/preflightLogic' import { FeatureFlagType } from '~/types' interface InstructionsModalProps { - featureFlag: FeatureFlagType + flag: FeatureFlagType['key'] visible: boolean onClose: () => void } -export function InstructionsModal({ onClose, visible, featureFlag }: InstructionsModalProps): JSX.Element { +export function InstructionsModal({ onClose, visible, flag }: InstructionsModalProps): JSX.Element { const { preflight } = useValues(preflightLogic) const getCloudPanels = (): JSX.Element => ( @@ -38,12 +38,12 @@ export function InstructionsModal({ onClose, visible, featureFlag }: Instruction
Opt user in
- +
Opt user out
- +
Retrieve Previews @@ -61,12 +61,12 @@ export function InstructionsModal({ onClose, visible, featureFlag }: Instruction
Opt user in
- +
Opt user out
- +
Retrieve Previews @@ -91,19 +91,19 @@ export function InstructionsModal({ onClose, visible, featureFlag }: Instruction ) } -function FeatureEnrollInstructions({ featureFlag }: { featureFlag: FeatureFlagType }): JSX.Element { +function FeatureEnrollInstructions({ flag }: { flag: string }): JSX.Element { return ( - {`posthog.updateEarlyAccessFeatureEnrollment("${featureFlag.key}", true) + {`posthog.updateEarlyAccessFeatureEnrollment("${flag}", true) `} ) } -function FeatureUnenrollInstructions({ featureFlag }: { featureFlag: FeatureFlagType }): JSX.Element { +function FeatureUnenrollInstructions({ flag }: { flag: string }): JSX.Element { return ( - {`posthog.updateEarlyAccessFeatureEnrollment("${featureFlag.key}", false) + {`posthog.updateEarlyAccessFeatureEnrollment("${flag}", false) `} ) diff --git a/frontend/src/scenes/persons/PersonsTable.tsx b/frontend/src/scenes/persons/PersonsTable.tsx deleted file mode 100644 index 05ecbccc5233b..0000000000000 --- a/frontend/src/scenes/persons/PersonsTable.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import { IconTrash } from '@posthog/icons' -import { LemonButton } from '@posthog/lemon-ui' -import { useActions } from 'kea' -import { CopyToClipboardInline } from 'lib/components/CopyToClipboard' -import { PropertiesTable } from 'lib/components/PropertiesTable' -import { TZLabel } from 'lib/components/TZLabel' -import { LemonTable, LemonTableColumn, LemonTableColumns } from 'lib/lemon-ui/LemonTable' -import { PersonDeleteModal } from 'scenes/persons/PersonDeleteModal' -import { personDeleteModalLogic } from 'scenes/persons/personDeleteModalLogic' -import { personsLogic } from 'scenes/persons/personsLogic' - -import { PersonType, PropertyDefinitionType } from '~/types' - -import { PersonDisplay } from './PersonDisplay' - -interface PersonsTableType { - people: PersonType[] - loading?: boolean - hasPrevious?: boolean - hasNext?: boolean - loadPrevious?: () => void - loadNext?: () => void - compact?: boolean - extraColumns?: LemonTableColumns - emptyState?: JSX.Element -} - -export function PersonsTable({ - people, - loading = false, - hasPrevious, - hasNext, - loadPrevious, - loadNext, - compact, - extraColumns, - emptyState, -}: PersonsTableType): JSX.Element { - const { showPersonDeleteModal } = useActions(personDeleteModalLogic) - const { loadPersons } = useActions(personsLogic) - - const columns: LemonTableColumns = [ - { - title: 'Person', - key: 'person', - render: function Render(_, person: PersonType) { - return - }, - }, - ...(!compact - ? ([ - { - title: 'ID', - key: 'id', - render: function Render(_, person: PersonType) { - return ( -
- {person.distinct_ids.length && ( - - {person.distinct_ids[0]} - - )} -
- ) - }, - }, - { - title: 'First seen', - dataIndex: 'created_at', - render: function Render(created_at: PersonType['created_at']) { - return created_at ? : <> - }, - }, - { - render: function Render(_, person: PersonType) { - return ( - showPersonDeleteModal(person, () => loadPersons())} - icon={} - status="danger" - size="small" - /> - ) - }, - }, - ] as Array>) - : []), - ...(extraColumns || []), - ] - - return ( - <> - { - loadNext?.() - window.scrollTo(0, 0) - } - : undefined, - onBackward: hasPrevious - ? () => { - loadPrevious?.() - window.scrollTo(0, 0) - } - : undefined, - }} - expandable={{ - expandedRowRender: function RenderPropertiesTable({ properties }) { - return Object.keys(properties).length ? ( - - ) : ( - 'This person has no properties.' - ) - }, - }} - dataSource={people} - emptyState={emptyState ? emptyState : 'No persons'} - nouns={['person', 'persons']} - /> - - - ) -}