Skip to content

Commit

Permalink
use organization flags API in the ui
Browse files Browse the repository at this point in the history
  • Loading branch information
jurajmajerik committed Nov 8, 2023
1 parent 3bafd2a commit 1171689
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 23 deletions.
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`, {
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

0 comments on commit 1171689

Please sign in to comment.