Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(feature flags): use organization flags API in the UI #18477

Merged
merged 31 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
690833e
fix feature flags filters dropdown width
jurajmajerik Oct 31, 2023
8bbdfd5
add plain UI
jurajmajerik Nov 1, 2023
26b87c6
Update frontend/src/scenes/feature-flags/FeatureFlagProjects.tsx
jurajmajerik Nov 1, 2023
8b4eddc
Update UI snapshots for `chromium` (2)
github-actions[bot] Nov 1, 2023
b292f82
move copy form out of the modal
jurajmajerik Nov 2, 2023
71d499e
move copy form out of the modal
jurajmajerik Nov 2, 2023
7f2409d
Merge branch 'fix/feature-flags-filters' of https://github.com/PostHo…
jurajmajerik Nov 2, 2023
bf314d4
add endpoint and tests
jurajmajerik Nov 3, 2023
25cc27f
Merge branch 'master' of https://github.com/PostHog/posthog into fix/…
jurajmajerik Nov 3, 2023
f443386
fix validation error msg
jurajmajerik Nov 3, 2023
3fba00f
Update UI snapshots for `chromium` (2)
github-actions[bot] Nov 3, 2023
2af4360
use ViewSet instead of APIView
jurajmajerik Nov 3, 2023
26f35c1
Merge branch 'fix/feature-flags-filters' of https://github.com/PostHo…
jurajmajerik Nov 3, 2023
aaf60a9
change lookup field from pk to feature_flag_key
jurajmajerik Nov 6, 2023
c98781f
add bulk copy endpoint
jurajmajerik Nov 6, 2023
5753263
resolve conflicts
jurajmajerik Nov 6, 2023
fa378bf
address feedback
jurajmajerik Nov 7, 2023
09f7c47
add snapshots
jurajmajerik Nov 7, 2023
567d5ad
Update query snapshots
github-actions[bot] Nov 7, 2023
91db420
Update query snapshots
github-actions[bot] Nov 7, 2023
fa5a029
snapshot
neilkakkar Nov 7, 2023
fe14a41
Update query snapshots
github-actions[bot] Nov 7, 2023
81b5328
adjust test_copy_feature_flag_create_new
jurajmajerik Nov 7, 2023
9b0b415
add test_copy_feature_flag_update_existing
jurajmajerik Nov 7, 2023
b9e0e65
clean up
jurajmajerik Nov 7, 2023
d61a86f
add typing for data in test_copy_feature_flag_missing_fields
jurajmajerik Nov 7, 2023
3bafd2a
Merge branch 'master' of https://github.com/PostHog/posthog into fix/…
jurajmajerik Nov 8, 2023
1171689
use organization flags API in the ui
jurajmajerik Nov 8, 2023
651fc84
define endpoints in api.ts
jurajmajerik Nov 8, 2023
07bfe1e
define endpoints in api.ts
jurajmajerik Nov 8, 2023
fe5d5c1
Merge branch 'fix/feature-flags-filters' of https://github.com/PostHo…
jurajmajerik Nov 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions frontend/src/scenes/feature-flags/FeatureFlag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,15 @@ export function FeatureFlag({ id }: { id?: string } = {}): JSX.Element {
})
}

const hasMultipleProjects = (currentOrganization?.teams?.length ?? 0) > 1
if (featureFlags[FEATURE_FLAGS.MULTI_PROJECT_FEATURE_FLAGS] && hasMultipleProjects) {
tabs.push({
label: 'Projects',
key: FeatureFlagsTab.PROJECTS,
content: <FeatureFlagProjects />,
})
}

if (featureFlags[FEATURE_FLAGS.FF_DASHBOARD_TEMPLATES] && featureFlag.key && id) {
tabs.push({
label: (
Expand Down Expand Up @@ -206,15 +215,6 @@ export function FeatureFlag({ id }: { id?: string } = {}): JSX.Element {
})
}

const hasMultipleProjects = (currentOrganization?.teams?.length ?? 0) > 1
if (featureFlags[FEATURE_FLAGS.MULTI_PROJECT_FEATURE_FLAGS] && hasMultipleProjects) {
tabs.push({
label: 'Projects',
key: FeatureFlagsTab.PROJECTS,
content: <FeatureFlagProjects />,
})
}

return (
<>
<div className="feature-flag">
Expand Down
60 changes: 47 additions & 13 deletions frontend/src/scenes/feature-flags/FeatureFlagProjects.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,63 @@
import { LemonTable, LemonTableColumns } from 'lib/lemon-ui/LemonTable'
import { LemonButton, LemonSelect } from '@posthog/lemon-ui'
import { LemonButton, LemonSelect, LemonTag, Link } from '@posthog/lemon-ui'
import { IconArrowRight, IconSync } from 'lib/lemon-ui/icons'
import { useActions, useValues } from 'kea'
import { LemonBanner } from 'lib/lemon-ui/LemonBanner'
import { featureFlagLogic } from './featureFlagLogic'
import { organizationLogic } from '../organizationLogic'
import { teamLogic } from 'scenes/teamLogic'
import { userLogic } from 'scenes/userLogic'
import { useEffect } from 'react'

const getColumns = (): LemonTableColumns<Record<string, string>> => {
const { currentTeamId } = useValues(teamLogic)
const { currentOrganization } = useValues(organizationLogic)
const { updateCurrentTeam } = useActions(userLogic)

return [
{
title: 'Project',
dataIndex: 'project_name',
render: (dataValue, record) =>
Number(record.project_id) === currentTeamId ? `${dataValue} (current)` : dataValue,
dataIndex: 'team_id',
render: (dataValue, record) => {
const team = currentOrganization?.teams?.find((t) => t.id === Number(dataValue))
if (!team) {
return '(project does not exist)'
}
const linkText = team.id === currentTeamId ? `${team.name} (current)` : team.name

return (
<Link
className="row-name"
onClick={() => {
updateCurrentTeam(team.id, `/feature_flags/${record.flag_id}`)
}}
>
{linkText}
</Link>
)
},
},
{
title: 'Flag status',
dataIndex: 'active',
render: (dataValue) => {
return dataValue ? 'active' : 'disabled'
return dataValue ? (
<LemonTag type="success" className="uppercase">
Enabled
</LemonTag>
) : (
<LemonTag type="default" className="uppercase">
Disabled
</LemonTag>
)
},
},
]
}

export default function FeatureFlagProjects(): JSX.Element {
const { featureFlag, copyDestinationProject, projectsWithCurrentFlag } = useValues(featureFlagLogic)
const { setCopyDestinationProject, loadProjectsWithCurrentFlag } = useActions(featureFlagLogic)
const { featureFlag, copyDestinationProject, projectsWithCurrentFlag, featureFlagCopyLoading } =
useValues(featureFlagLogic)
const { setCopyDestinationProject, loadProjectsWithCurrentFlag, copyFlag } = useActions(featureFlagLogic)
const { currentOrganization } = useValues(organizationLogic)
const { currentTeam } = useValues(teamLogic)

Expand All @@ -56,6 +83,7 @@ export default function FeatureFlagProjects(): JSX.Element {
<div>
<div className="font-semibold leading-6 h-6">Destination project</div>
<LemonSelect
dropdownMatchSelectWidth={false}
value={copyDestinationProject}
onChange={(id) => setCopyDestinationProject(id)}
options={
Expand All @@ -68,14 +96,20 @@ export default function FeatureFlagProjects(): JSX.Element {
</div>
<div>
<div className="h-6" />
<LemonButton type="primary" icon={<IconSync />}>
Copy
<LemonButton
disabledReason={!copyDestinationProject && 'Select destination project'}
loading={featureFlagCopyLoading}
type="primary"
icon={<IconSync />}
onClick={() => copyFlag()}
className="w-28 max-w-28"
>
{projectsWithCurrentFlag.find((p) => Number(p.team_id) === copyDestinationProject)
? 'Update'
: 'Copy'}
</LemonButton>
</div>
</div>
<LemonBanner type="warning" className="mb-6">
By performing the copy, you may overwrite your existing Feature Flag configuration in another project.
</LemonBanner>
<LemonTable
loading={false}
dataSource={projectsWithCurrentFlag}
Expand Down
48 changes: 47 additions & 1 deletion frontend/src/scenes/feature-flags/featureFlagLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ export interface FeatureFlagLogicProps {
id: number | 'new' | 'link'
}

export type ProjectsWithCurrentFlagResponse = {
flag_id: number
team_id: number
active: boolean
}[]

// KLUDGE: Payloads are returned in a <variant-key>: <payload> mapping.
// This doesn't work for forms because variant-keys can be updated too which would invalidate the dictionary entry.
// If a multivariant flag is returned, the payload dictionary will be transformed to be <variant-key-index>: <payload>
Expand Down Expand Up @@ -579,7 +585,32 @@ export const featureFlagLogic = kea<featureFlagLogicType>([
projectsWithCurrentFlag: {
__default: [] as Record<string, string>[],
loadProjectsWithCurrentFlag: async () => {
return []
const orgId = values.currentOrganization?.id
const flagKey = values.featureFlag.key

const projects: ProjectsWithCurrentFlagResponse = await api.get(
`api/organizations/${orgId}/feature_flags/${flagKey}`
)

// Put current project first
const currentProjectIdx = projects.findIndex((p) => p.team_id === values.currentTeamId)
if (currentProjectIdx) {
const [currentProject] = projects.splice(currentProjectIdx, 1)
const sortedProjects = [currentProject, ...projects]
return sortedProjects
}
return projects
},
},
featureFlagCopy: {
copyFlag: async () => {
const orgId = values.currentOrganization?.id

return await api.create(`api/organizations/${orgId}/feature_flags/copy_flags`, {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feature_flag_key: values.featureFlag.key,
from_project: values.currentTeamId,
target_project_ids: [values.copyDestinationProject],
})
},
},
})),
Expand Down Expand Up @@ -742,6 +773,21 @@ export const featureFlagLogic = kea<featureFlagLogicType>([
featureFlagsLogic.findMounted()?.actions.updateFlag(updatedFlag)
}
},
copyFlagSuccess: ({ featureFlagCopy }) => {
if (featureFlagCopy.success.length) {
const operation = values.projectsWithCurrentFlag.find(
(p) => Number(p.team_id) === values.copyDestinationProject
)
? 'updated'
: 'copied'
lemonToast.success(`Feature flag ${operation} successfully!`)
} else {
lemonToast.error(`Error while saving feature flag: ${featureFlagCopy.failed || featureFlagCopy}`)
}

actions.loadProjectsWithCurrentFlag()
actions.setCopyDestinationProject(null)
},
})),
selectors({
sentryErrorCount: [(s) => [s.sentryStats], (stats) => stats.total_count],
Expand Down
Loading